0

I have a after insert trigger on an object that calls a @future method to do a callout to an external system.

If the callout fails I want it to update the original object.

I am batching up 100 at a time and there are timeouts on the callout, but the record is not being updated.

The debug log is showing an update but the object is not updated. Any thoughts?

Trigger

/**
 * Copyright (c) 2012, FinancialForce.com, inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 * - Neither the name of the FinancialForce.com, inc nor the names of its contributors 
 *      may be used to endorse or promote products derived from this software without 
 *      specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 *  THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 *  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/
trigger SyncLog_TRG on SyncLog__c (after delete, after insert, after update, before delete, before insert, before update) 
{
    // Creates Domain class instance and calls appropriate overideable methods according to Trigger state
    if (!System.isFuture()) {
        SObjectDomain.triggerHandler(DOM_SyncLogs.class);
    }
}

DOM_Synclogs.class

/* **************************************************************************************** */
public with sharing class DOM_SyncLogs extends SObjectDomainLocal {

    /* *************************************************************************************** */
    public DOM_SyncLogs(List<SyncLog__c> sObjectList) {
        // Domain classes are initialised with lists to enforce bulkification throughout
        super(sObjectList);
    }

    /* *************************************************************************************** */
    public class Constructor implements SObjectDomain.IConstructable
    {
        public SObjectDomain construct(List<SObject> sObjectList)
        {
            return new DOM_SyncLogs(sObjectList);
        }
    }

    /* *************************************************************************************** */
    /* Beginning trigger handlers
    /* *************************************************************************************** */
    /* *************************************************************************************** */
    public override void afterInsert()
    {
        super.afterInsert();
        SVC_SyncLogService.sendSyncLogFuture(new List<ID>(Trigger.newMap.keySet()));
    }
}

SVC_SyncLogService

    /******************************************************************************************************************/
    /** sendSyncLogFuture
    * @author       MCAMP
    * @date         08/15/2019
    * @description  in Future Send the SyncLog to iClassPro - **************** Demonstrates using @future ****************
    * @param        List<ID> List of Synclog IDs.  - **************** @future required primative types or collections of primitive types ****************
    * @return       
    */
    @future (callout=true)
    public static void sendSyncLogFuture(List<ID> SyncLogList) {
        sendSyncLog(SyncLogList);
    }

    /******************************************************************************************************************/
    /** sendSyncLog
    * @author       MCAMP
    * @date         08/15/2019
    * @description  Send the SyncLog to iClassPro - **************** Demonstrates using @future ****************
    * @param        List<ID> List of Synclog IDs.
    * @return       
    */
    public static void sendSyncLog(List<ID> SyncLogList) {
        //place any code needed before sending to manager here
        // use list of IDs to get list of SyncLogs 
        // MGR_iCampPro.sendSyncLog(SyncLogRecordList);
        DM_SyncLogSelector querySelector = new DM_SyncLogSelector();
        Set<Id> SyncLogIds = new Set<Id>();
        for (Id SyncLogId: SyncLogList) {
            SyncLogIds.add(SyncLogId);
        }
        List<SyncLog__c> SyncLogRecordList = querySelector.selectSObjectsById(SyncLogIds);
        //if running as part of a test class do not call manager
        if(!Test.isRunningTest()){
            MGR_iCampPro MgrICampPro = new MGR_iCampPro();
            MgrICampPro.sendSyncLog(SyncLogRecordList);
        }
    }

MGR_iCampPro

    public HttpResponse sendSyncLog(List<SyncLog__c> SyncLogList) {

        Http http = new Http();
        HttpRequest req = new HttpRequest();
        iCampPro_Settings__c iCampSettings = iCampPro_Settings__c.getOrgDefaults();
        string endPoint = iCampSettings.iCampPro_Base_URL__c + iCampSettings.iCampPro_Api_Url__c + iCampSettings.SyncLog_Endpoint__c;
        req.setEndpoint(endPoint);
        req.setHeader('Content-Type','application/x-www-form-urlencoded') ;
        req.setHeader('Accept','application/json') ;     
        req.setMethod('POST');
        HttpResponse res;
        for (SyncLog__c SyncLogObject: SyncLogList) {
            String email = '';
            String firstName = '';
            String lastName = '';
            Boolean adult = false;
            if(SyncLogObject.Email__c != null){email = EncodingUtil.UrlEncode(SyncLogObject.Email__c,'UTF-8');}
            if(SyncLogObject.FirstName__c != null){firstName = EncodingUtil.UrlEncode(SyncLogObject.FirstName__c,'UTF-8');}
            if(SyncLogObject.LastName__c != null){lastName = EncodingUtil.UrlEncode(SyncLogObject.LastName__c,'UTF-8');}
            if(SyncLogObject.Confirmed_Adult__c != null){adult = SyncLogObject.Confirmed_Adult__c;}
            String bodyParams = 'objectId=' + SyncLogObject.Id +
            '&accountDefaultContact=' + SyncLogObject.Account_Default_Contact__c +
            '&accountId=' + SyncLogObject.AccountId__c +
            '&contact=' + SyncLogObject.Contact__c +
            '&contactId=' + SyncLogObject.ContactID__c +
            '&createdById=' + SyncLogObject.CreatedById +
            '&email=' + email +
            '&firstName=' + firstName +
            '&gender=' + SyncLogObject.Gender__c +
            '&homePhone=' + SyncLogObject.HomePhone__c +
            '&lastModifiedById=' + SyncLogObject.LastModifiedById +
            '&lastName=' + lastName +
            '&mailingCity=' + SyncLogObject.MailingCity__c +
            '&mailingCountry=' + SyncLogObject.MailingCountry__c +
            '&mailingPostalCode=' + SyncLogObject.MailingPostalCode__c +
            '&mailingState=' + SyncLogObject.MailingState__c +
            '&mailingStreet=' + SyncLogObject.MailingStreet__c +
            '&middleName=' + SyncLogObject.Middle_Name__c +
            '&mobilePhone=' + SyncLogObject.MobilePhone__c +
            '&ownerId=' + SyncLogObject.OwnerId +
            '&recordVersion=' + SyncLogObject.Record_Version__c +
            '&status=' + SyncLogObject.Status__c +
            '&statusDetail=' + SyncLogObject.Status_Detail__c +
            '&statusLastUpdated=' + SyncLogObject.Status_Last_Updated__c +
            '&name=' + SyncLogObject.Name +
            '&grade=' + SyncLogObject.Grade__c +
            '&birthDate=' + SyncLogObject.Birthdate__c +
            '&adult=' + adult;
            req.setBody(bodyParams);

            try {
                res = http.send(req);
            } catch (exception e) {
                //Need to mark as error here
                SyncLog__c syncLogUpdate = new SyncLog__c();
                syncLogUpdate.Id = SyncLogObject.Id;
                syncLogUpdate.Status__c='Failed';
                syncLogUpdate.Status_Detail__c=e.getMessage();
                syncLogUpdate.Status_Last_Updated__c=System.now();
                update syncLogUpdate;
                throw e;
            }
        }
        return res;
    }
0

1 Answer 1

1

You cannot both throw an exception that is not ultimately handled and successfully complete DML in the same transaction. That's what's happening here:

        try {
            res = http.send(req);
        } catch (exception e) {
            //Need to mark as error here
            SyncLog__c syncLogUpdate = new SyncLog__c();
            syncLogUpdate.Id = SyncLogObject.Id;
            syncLogUpdate.Status__c='Failed';
            syncLogUpdate.Status_Detail__c=e.getMessage();
            syncLogUpdate.Status_Last_Updated__c=System.now();
            update syncLogUpdate;
            throw e;
        }

When you re-throw the exception with throw e, your DML update is rolled back (along with the entirety of the transaction).

If you wish to be able to persist your updates, do not re-throw the exception - catch it and handle it.

2
  • Thanks, I'll check that out.
    – M. Camp
    Commented Feb 18, 2020 at 14:59
  • That fixed it. Sometimes it takes a second set of eyes. Thanks
    – M. Camp
    Commented Feb 18, 2020 at 20:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .