function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
tonantetonante 

How do I avoid a condition where Batch Job indirectly and unknowingly calls the Future method of a trigger's supporting class?

HI I am running a batch class that sets a field on account objects based on a condition and then it loads the account into a list of accounts to be bulk updated. However there is a trigger which fires on Account After Update that interrupts the Bacth job when it tries to do the DML_BEGIN (Update). That trigger takes over and attempts to process the accounts and send the info to Demandware service using a  supporting Controller Class that calls a Future Method. Thus this Batch job indirectly calls Future method so I get an exception. We need the batch job to reset the field and update the account and then we want the rest controller having the future method be able to pass this info to the Demandware server account. Thanks for your help.

1) Batch Job class:
global class BatchCancelMozoFreeTrialObject  implements Database.Batchable<sObject> {
// global variables	
	global String query;
	global class UtilException extends Exception {} 
	
	global database.querylocator start(Database.BatchableContext BC)
	{
		// Select All Accounts that have Current as their free trail Status
		if(query == null) 
		{
			query 
				= 'Select Id, '
				+ ' Mozo_Trial_End_Date__c, ' 
				+ '	Mozo_Trial_Status__c '
				+ 'From Account WHERE Mozo_Trial_Status__c = \'Current\' '
				+ ' And (RecordType.Name like \'%Church%\' Or RecordType.Name like \'%Organization%\')';
		}
		system.debug('<< QUERY  >> '+query);	
		return Database.getQueryLocator(query);
	}
 	
	global void execute(Database.BatchableContext BC, List<sObject> scope)
	{
		
		List<Account> accountsToUpdate = new List<Account>();
		List<ID> storeContactIDs = new List<ID>();
		for(sObject s : scope)
		{
			Account thisAccount = (Account)s;
			system.debug('<<DEBUG STATUS >> '+thisAccount.Id+' <<ID>> '+thisAccount.Mozo_Trial_Status__c+'<<  DATE  >> '+thisAccount.Mozo_Trial_End_Date__c+' VS  '+system.today());
			if(thisAccount.Mozo_Trial_End_Date__c <= system.today() && thisAccount.Mozo_Trial_Status__c == 'Current') { 		 
				thisAccount.Mozo_Trial_Status__c = 'Former';
				accountsToUpdate.add(thisAccount);
			}
			  
		}
		
		system.debug(accountsToUpdate);
		if(accountsToUpdate.size() > 0){
			update accountsToUpdate;
		}
	}

	global void finish(Database.BatchableContext BC)
	{

	}

}
2) Trigger which called Controller Method:
trigger updateMozoTrialToDWREAccount on Account (after update) {
    
    List<String> accountIds = new List<String>();
    List<Account> accountList = new List<Account>();
    String doNotCallController = 'success';
    RecordType rec = [select Id from RecordType where Name  = 'US Organization' limit 1];
    User demandwareAppUser = [select Id from User where Name = 'Demandware'];
    if(Trigger.isUpdate){
        system.debug('--------------------'+demandwareAppUser.Id);
        for(Account accs: Trigger.new){
            if(accs.RecordTypeId == rec.Id && accs.LastModifiedById != demandwareAppUser.Id){
                system.debug('Account Details'+'--'+accs.Name+'--'+accs.RecordTypeId+'--'+accs.LastModifiedById);
                accountList.add(accs);
            }
        }
    }
    for(Integer count = 0; count < accountList.size() ; count++){
        if(trigger.old[count].Mozo_Trial_Status__c != trigger.new[count].Mozo_Trial_Status__c || 
           trigger.old[count].Mozo_Free_Trial_ContactID__c != trigger.new[count].Mozo_Free_Trial_ContactID__c ||
           trigger.old[count].Mozo_Trial_Start_Date__c != trigger.new[count].Mozo_Trial_Start_Date__c ||
           trigger.old[count].Mozo_Trial_End_Date__c != trigger.new[count].Mozo_Trial_End_Date__c){
            
            system.debug('--'+trigger.new[count].Id+'--'+trigger.new[count].Mozo_Free_Trial_ContactID__c+'--'+trigger.new[count].Mozo_Trial_Status__c+'--'+trigger.new[count].Mozo_Trial_Start_Date__c+'--'+trigger.new[count].Mozo_Trial_End_Date__c);
            //DemandwareController.getAccessToken(trigger.new[count].Id,trigger.new[count].Mozo_Free_Trial_ContactID__c,trigger.new[count].Mozo_Trial_Status__c,trigger.new[count].Mozo_Trial_Start_Date__c,trigger.new[count].Mozo_Trial_End_Date__c);
            
            if( trigger.old[count].Mozo_Trial_Status__c == 'Former' && trigger.old[count].Mozo_Trial_End_Date__c == trigger.new[count].Mozo_Trial_End_Date__c && (trigger.new[count].LastModifiedById == '00550000000vuyG' || trigger.new[count].LastModifiedById == '00550000001qiyX')){
                doNotCallController = 'fail';
            }
            if(doNotCallController == 'success'){
                AccountHelper ah = new AccountHelper(trigger.new[count].Id);
                accountIds.add(JSON.serialize(ah));  
                ControllerToUpdateDWREAccount.getAccessToken(accountIds);
            }
        }
    }
    
}
3. Controller used by Trigger that has the Future method:
Global class ControllerToUpdateDWREAccount {
    @future (callout=true)
    
      global static void getAccessToken(List<String> accountIds){
    //system.debug(accId+' '+MozoTContactId+' '+MozoTStatus+' '+MozoTStartDate+' '+MozoTEndDate);
        String accErrorId;
        AccountHelper currIds = null;
        List<Account> accList = new List<Account>();
        //String exceptionString = 'MethodNot';
        try{
            string accToken='';
            Http http = new Http(); 
            HttpRequest req = new HttpRequest();
            HttpRequest req1 = new HttpRequest();
            HttpRequest reqUpdate = new HttpRequest(); 
            
            for (String ser : accountIds)
           {
            
             currIDs = (AccountHelper) JSON.deserialize(ser, AccountHelper.class);
             System.debug('Deserialized in future:'+currIds.Id);     
             accErrorId = currIds.Id;
             Account a = [SELECT Id,Mozo_Free_Trial_ContactID__c,Mozo_Trial_Status__c,Mozo_Trial_Start_Date__c,Mozo_Trial_End_Date__c FROM Account WHERE id =: currIds.Id];
                                
            req.setMethod('POST');
            req.setHeader('Authorization','Basic NjQ3NTMxYTktNzZmYy00ZDNhLThjYTMtNmNlNDA5ZmYxMTcxOjcvWFItQSptYWQsRUpNQE5LZD5s');
            //req.setHeader('Authorization','Basic 923d2a4c-b33b-4e3c-ad0a-370afbd46abb');
            
            req.setHeader('Content-Type','application/x-www-form-urlencoded');  
            req.setEndpoint('https://account.demandware.com/dw/oauth2/access_token?grant_type=client_credentials'); 
            
                HTTPResponse res = http.send(req); 
                JSONParser parser = JSON.createParser(res.getBody());
                while (parser.nextToken() != null) {
                    if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && (parser.getCurrentName() == 'access_token')) {
                        parser.nextToken();
                        accToken = parser.getText();    
                    }
                }      
                system.debug('-----Demandware Access Token-------'+ accToken);
                if(accToken != null || accToken != ''){
                    req1.setMethod('GET');
                    req1.setHeader('Authorization','Bearer ' + accToken);
                    //req1.setEndpoint('https://staging-web-awana.demandware.net/s/-/dw/data/v16_3/custom_objects/Account/0015000000GUZYzAAP');
                    req1.setEndpoint('https://staging.store.awana.org/s/-/dw/data/v16_3/custom_objects/Account/'+a.Id);
                  
                    HTTPResponse res1 = http.send(req1); 
                    system.debug('*******************'+res1.getBody());
                    string[] headerkeys = res1.getHeaderKeys();
                    string headerValue;
                    for(string s : headerkeys){
                        if(s == 'Etag'){
                            headerValue = res1.getHeader(s);
                            system.debug('header: ' + s + ' value: ' + headerValue);
                        }
                    }
                    if(headerValue != '' || headerValue != null){
                        
                        reqUpdate.setHeader('Authorization','Bearer ' + accToken);
                        reqUpdate.setHeader('Content-Type','application/json; charset=UTF-8');
                        reqUpdate.setHeader('If-Match',headerValue);
                        Map<String,object> jsonObject = new Map<String,object>();
                        //jsonObject.put('c_MozoTrialStatus','Current');
                        jsonObject.put('c_MozoTrialStatus', a.Mozo_Trial_Status__c);
                        jsonObject.put('c_MozoTrialContactID', a.Mozo_Free_Trial_ContactID__c);
                        Integer d = a.Mozo_Trial_Start_Date__c.day();
                        Integer mo = a.Mozo_Trial_Start_Date__c.month();
                        Integer yr = a.Mozo_Trial_Start_Date__c.year();
                        
                        DateTime ST = DateTime.newInstance(yr, mo, d, 12, 0, 0);
                        jsonObject.put('c_MozoTrialStartDate', ST);
                        
                        Integer ed = a.Mozo_Trial_End_Date__c.day();
                        Integer emo = a.Mozo_Trial_End_Date__c.month();
                        Integer eyr = a.Mozo_Trial_End_Date__c.year();
                        
                        DateTime ET = DateTime.newInstance(eyr, emo, ed, 12, 0, 0);
                        
                        jsonObject.put('c_MozoTrialEndDate', ET);
                        String JSONString = JSON.serialize(jsonObject);   
                        reqUpdate.setBody(JSONString);
                        system.debug(JSONString);
                        reqUpdate.setEndpoint('https://staging.store.awana.org/s/-/dw/data/v16_3/custom_objects/Account/'+a.Id);  
                        
                        reqUpdate.setMethod('POST');
                        reqUpdate.setHeader('x-dw-http-method-override','PATCH');
   
                        
                            HTTPResponse resUpdate = http.send(reqUpdate); 
                            system.debug('*******************'+resUpdate.getBody());
                            
                    }
                }   
           }//for   
        }//try outer
       catch(Exception ne){
            system.debug('Exception '+ne);
            Messaging.SingleEmailMessage mail=new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {'shaikbaji.a@ecgroup-intl.com'};
            mail.setToAddresses(toAddresses);
            mail.setReplyTo('shaikbaji.a@ecgroup-intl.com');
            mail.setSenderDisplayName('Salesforce');
            mail.setSubject('Update Failed');
            mail.setPlainTextBody('Account Id: '+accErrorId+' not existed in Demandware');
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
            accErrorId = null;
        }
    }
}



 
Best Answer chosen by tonante
tonantetonante
Well tha idea didn;t work but what we found was that if we add the interface Database.Callouts to the Batch class then we can get the batch class to process the account objects and bulk update them without any interference from the Demandware trigger:

global class BatchCancelMozoFreeTrialObject  implements Database.Batchable<sObject>, 
Database.AllowsCallouts {
// global variables	
	global String query;
	global class UtilException extends Exception {} 
	
	global database.querylocator start(Database.BatchableContext BC)
	{
		// Select All Accounts that have Current as their free trail Status
		if(query == null) 
		{
			query 
				= 'Select Id, '
				+ ' Mozo_Trial_End_Date__c, ' 
				+ '	Mozo_Trial_Status__c '
				+ 'From Account WHERE Mozo_Trial_Status__c = \'Current\' '
				+ ' And (RecordType.Name like \'%Church%\' Or RecordType.Name like \'%Organization%\')';
		}
		system.debug('<< QUERY  >> '+query);	
		return Database.getQueryLocator(query);
	}
 	
	global void execute(Database.BatchableContext BC, List<sObject> scope)
	{
		
		List<Account> accountsToUpdate = new List<Account>();
		List<ID> storeContactIDs = new List<ID>();
		for(sObject s : scope)
		{
			Account thisAccount = (Account)s;
			system.debug('<<DEBUG STATUS >> '+thisAccount.Id+' <<ID>> '+thisAccount.Mozo_Trial_Status__c+'<<  DATE  >> '+thisAccount.Mozo_Trial_End_Date__c+' VS  '+system.today());
			if(thisAccount.Mozo_Trial_End_Date__c <= system.today() && thisAccount.Mozo_Trial_Status__c == 'Current') { 		 
				thisAccount.Mozo_Trial_Status__c = 'Former';
				accountsToUpdate.add(thisAccount);
			}
			  
		}
		
		system.debug(accountsToUpdate);
		if(accountsToUpdate.size() > 0){
			update accountsToUpdate;
		}
	}

	global void finish(Database.BatchableContext BC)
	{

	}

}

 

All Answers

Veenesh VikramVeenesh Vikram
Hi,

You can use the System.isBatch() & System.isFuture() method in your use case. These methods will enable you to check if the update is coming from a Batch, if yes, you cn skip calling the future method and alter your logic accordingly.

Refer: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_system.htm

Hope this helps!

Veenesh
tonantetonante
Thanks Vikram I will try this and ge back to you.
tonantetonante
Vernesh: the reply looks good but the problem is if we skips the batch then that account will not have the updated info in Demandware
tonantetonante
I may have a solution Veemesh which incorporates your solution but it would take a copy of the global static method and put it in the batch job code so now the trigger only runs when batch has not run but batch can then write to the demandware server and still update accounts possibly without using future method. Thus future method will only be called by trigger.
tonantetonante
Well tha idea didn;t work but what we found was that if we add the interface Database.Callouts to the Batch class then we can get the batch class to process the account objects and bulk update them without any interference from the Demandware trigger:

global class BatchCancelMozoFreeTrialObject  implements Database.Batchable<sObject>, 
Database.AllowsCallouts {
// global variables	
	global String query;
	global class UtilException extends Exception {} 
	
	global database.querylocator start(Database.BatchableContext BC)
	{
		// Select All Accounts that have Current as their free trail Status
		if(query == null) 
		{
			query 
				= 'Select Id, '
				+ ' Mozo_Trial_End_Date__c, ' 
				+ '	Mozo_Trial_Status__c '
				+ 'From Account WHERE Mozo_Trial_Status__c = \'Current\' '
				+ ' And (RecordType.Name like \'%Church%\' Or RecordType.Name like \'%Organization%\')';
		}
		system.debug('<< QUERY  >> '+query);	
		return Database.getQueryLocator(query);
	}
 	
	global void execute(Database.BatchableContext BC, List<sObject> scope)
	{
		
		List<Account> accountsToUpdate = new List<Account>();
		List<ID> storeContactIDs = new List<ID>();
		for(sObject s : scope)
		{
			Account thisAccount = (Account)s;
			system.debug('<<DEBUG STATUS >> '+thisAccount.Id+' <<ID>> '+thisAccount.Mozo_Trial_Status__c+'<<  DATE  >> '+thisAccount.Mozo_Trial_End_Date__c+' VS  '+system.today());
			if(thisAccount.Mozo_Trial_End_Date__c <= system.today() && thisAccount.Mozo_Trial_Status__c == 'Current') { 		 
				thisAccount.Mozo_Trial_Status__c = 'Former';
				accountsToUpdate.add(thisAccount);
			}
			  
		}
		
		system.debug(accountsToUpdate);
		if(accountsToUpdate.size() > 0){
			update accountsToUpdate;
		}
	}

	global void finish(Database.BatchableContext BC)
	{

	}

}

 
This was selected as the best answer
tonantetonante
http://salesforce-walker.blogspot.com/2013/09/callouts-in-batch-apexstate-in-batch.html