You need to sign in to do that
Don't have an account?
Sunay - KVP Bus Solns
Calling future method from a trigger
Hi,
I am using a trigger to call the future method, but it gives me a error which says "Too many Future calls". There are 100 of records inserted, after which this method is called. Is there any way where I can overcome this error.
Please give me some inputs on this.
Thanks.
Alright so here's my plan:
Instead of using a class that has a future method that can make just 10 requests for all your records, create a batch apex class that processes the record requests one at a time. The execute() method of the class will use the 10 calls just for 1 record, you can include a retry mechanism in case they time out. Since the batch is executed asynchronously you should be able to process a very large number of records like this.
1. create a batch apex class. See documentation here: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_batch_interface.htm
You'll need to create a start, execute and a finish method.
2. the constructor of the batch should have a Set<String> argument so you can send the list of order numbers. Initialize a class variable with it so you can use it in the start() method when extracting the list of records
3. use Database.QueryLocator instead of Iterable when creating the start().
4. reuse your httprequest code from the future method above in the execute(). You'll only need to process 1 record. Put some sort of warning(email etc) when a callout fails...
5. in the trigger you'll call your class with smth like this:
"1" means just 1 record from the list return by the start qerylocator is proccessed at a time by the execute().
Hope this helps and sorry for the late answer, got swamped with mail and work :) ...
Adrian
All Answers
Hi,
You can write your future method in a class and then call that method in your trigger.
Did this answer your question? If not, let me know what didn't work, or if so, please mark it solved.
I have done that, since more than 100 records are inserting at a time, its giving me the error "Too Many future calls" as I understand that it has a limit of 10. And I need to overcome this, can you please suggest me a way
There is a limit of a maximum of 10 asynchronous calls within the context of a single Apex logical transaction. You are hitting this limit because your trigger calls the method more than ten times.
Can you post your code so we can take a look?
Why do you need the future calls?
Have a nice day,
Adrian
Instead of calling the @future method 100 times, call it once and provida as parameters a list of the data you need.
(if you are providing 3 strings, change it to provide 3 List<String> and inside the @future method, loop over you elements)
Regards
I am inserting records through a webservice callout in a custom object, so on insertion of records, I would like to run a different class which will update the records with the data coming from a webservice callout. Basically on one call out records are inserted, and trigger is executed which will call the method, and upon execution of this method, there is one more http callout which will get other data that has to be updated in the records created earlier.
This is the trigger which is calling the method:
trigger listorderitems on Order_Details__c (after insert)
{
List<Order_Details__c> ordlist = new List<Order_Details__c>();
Set<Id> ordid = new Set<Id>();
for (Order_Details__c ord : trigger.new)
{
//ordlist.add(ord.Id);
ordid.add(ord.Id);
}
List<Order_Details__c> orderlist = [Select id,Order_Number__c from Order_Details__c where id = :ordid];
for(Order_Details__c order: orderlist)
{
ListOrderItems.Listorderitems(order.Order_Number__c);
}
}
This is the apex class through which the method is executing:
global class ListOrderItems{
@future(callout=true)
static webservice void Listorderitems(String orderid)
{
Order_Details__c ord = [Select id, Order_Number__c from Order_Details__c where Order_Number__c=:orderid];
// The date format is yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
DateTime d = System.now();
String timestamp = d.format('yyyy-MM-dd')+'T'+ d.format('HH:mm:ss')+'Z';
System.debug(timestamp);
String urlEncodedTimestamp = EncodingUtil.urlEncode(timestamp, 'UTF-8');
String action = 'ListOrderItems';
//This date is used for the field CreatedAfter
DateTime dt = System.now() - 1;
System.debug(dt);
String dtime = dt.format('yyyy-MM-dd')+'T'+ dt.format('HH:mm:ss')+'.'+ dt.format('SSS')+'Z';
System.debug(dtime);
String urlEncodeddtime = EncodingUtil.urlEncode(dtime, 'UTF-8');
String AmazonOrderId = ord.Order_Number__c;
String SellerSkus = 'TJ-06NB-VVTF';
String urlToTest = 'amazon.com';
String version = '2011-01-01';
String SignatureVersion = '2';
String SignatureMethod = 'HmacSHA256';
String MarketplaceId= 'SAMPLEMARKETPLACEID';
String FulfillmentChannel = 'MFN';
String SellerId = 'SAMPLESELLERID';
String endpoint = 'https://mws.amazonservices.com/Orders/2011-01-01';
String accessKey = 'SAMPLEACCESSKEY';
String ResponseGroup = 'Detailed';
String QueryStartDateTime = urlEncodeddtime;
String inputstr = 'AWSAccessKeyId=' + accessKey +
'&Action=' + action +
'&AmazonOrderId=' + AmazonOrderId +
'&SellerId=' + SellerId +
'&SignatureMethod=' + SignatureMethod +
'&SignatureVersion=' + SignatureVersion +
'&Timestamp=' + urlEncodedTimestamp +
'&Version=' + version ;
System.debug(inputstr);
String StringtoSign = 'POST\n'+
'mws.amazonservices.com\n'+
'/Orders/2011-01-01\n' +
inputstr ;
//String StringtoSign = 'POST\nmws.amazonservices.com\n/Orders/2011-01-01\nAWSAccessKeyId=SAMPLEACCESSKEY&Action=ListOrderItems&AmazonOrderId=&SellerId=SAMPLESELLERID&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-06-11T07%3A15%3A44Z&Version=2011-01-01';
System.debug(StringtoSign);
Blob mac = Crypto.generateMac('HMacSHA256', blob.valueof(StringtoSign),blob.valueof('SAMPLESECRETKEY'));
String macUrl = EncodingUtil.base64Encode(mac);
//String signed = make_sig(StringtoSign);
//System.debug(signed);
String codedsigned = EncodingUtil.urlEncode(macUrl,'UTF-8');
System.debug(codedsigned);
HttpRequest req = new HttpRequest();
//req.setEndpoint('https://mws.amazonservices.com/Orders/2011-01-01?AWSAccessKeyId=SAMPLEACCESSKEY&Action=ListOrderItems&SellerId=SAMPLESELLERID&SignatureVersion=2&Timestamp=2012-06-11T07%3A15%3A44Z&Version=2011-01-01&Signature=KAuyZ13Sll8vuG2%2BGMUKy%2F6%2BjoH4Fgl78kHyGj6XMuQ%3D&SignatureMethod=HmacSHA256&AmazonOrderId=');
req.setEndpoint(endpoint + '?AWSAccessKeyId=' + accessKey +
'&Action=' + action +
'&SellerId=' + SellerId +
'&SignatureVersion='+SignatureVersion +
'&Timestamp=' + urlEncodedTimestamp +
'&Version=' + version +
'&Signature=' + codedsigned +
'&SignatureMethod='+ SignatureMethod +
'&AmazonOrderId=' + AmazonOrderId);
req.setMethod('POST');
Http http = new Http();
try {
HttpResponse res = http.send(req);
System.debug('STATUS:'+res.getStatus());
System.debug('STATUS_CODE:'+res.getStatusCode());
System.debug('BODY: '+res.getBody());
String xml = res.getBody();
XmlStreamReader xsr = new XmlStreamReader(xml);
System.debug(xsr);
List<Order_Details__c> orderlist = new List<Order_Details__c>();
if(xsr.getEventType()==XmlTag.START_DOCUMENT)
{
xsr.next();
xsr.next();
while(xsr.getEventType()!=XmlTag.END_DOCUMENT)
{
xsr.next();
if(xsr.getLocalName() == 'OrderItem')
{
xsr.next();
//Order_Details__c ord = new Order_Details__c();
while(xsr.getLocalName() != 'OrderItem')
{
if(xsr.getLocalName() == 'SellerSKU')
{
xsr.next();
if (xsr.getEventType() == XmlTag.CHARACTERS)
{
ord.SKU__c = xsr.getText().trim();
xsr.next();
}
xsr.next();
continue;
}
if(xsr.getLocalName() == 'Title')
{
xsr.next();
if (xsr.getEventType() == XmlTag.CHARACTERS)
{
ord.Title__c = xsr.getText().trim();
xsr.next();
}
xsr.next();
continue;
}
if(xsr.getLocalName() == 'QuantityOrdered')
{
xsr.next();
if (xsr.getEventType() == XmlTag.CHARACTERS)
{
ord.Quantity_in_Order__c= Decimal.valueOf(xsr.getText().trim());
xsr.next();
}
xsr.next();
continue;
}
xsr.next();
}
orderlist.add(ord);
}
// xsr.next();
}
}
update orderlist;
System.debug(orderlist);
} catch(System.CalloutException e) {
System.debug('ERROR: '+ e);
}
}
}
Alright.... @SeAlVa is right, one change would be to make your static webservice void Listorderitems take a List<String> instead of String. This will fix your "Too many Future calls" problem.
You will need to modify your httprequests code also. This is where I believe you'll run into more governor limits:
source: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_gov_limits.htm
I'll need to think a little more about this. A change of design might be needed. Donno why but I'm thinking batch apex might be better...
Thanks!
Adrian
Hi if I take the List<string>, I am not able to call this method in the trigger. Is there anything which I need to check in the trigger?
I believe future methods won't accept lists, but will accept a set. Try Set<String> instead. But Adrian is right, you're going to hit an issue with the number of callouts in your future method, batch apex will be the only way to accomplish this.
Ok I solved the issue of "Too Many Future Calls", but as Adrain mentioned now I am getting the error for too many callouts. And I am not sure how batch apex works, can you guys please help me on that.
Alright so here's my plan:
Instead of using a class that has a future method that can make just 10 requests for all your records, create a batch apex class that processes the record requests one at a time. The execute() method of the class will use the 10 calls just for 1 record, you can include a retry mechanism in case they time out. Since the batch is executed asynchronously you should be able to process a very large number of records like this.
1. create a batch apex class. See documentation here: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_batch_interface.htm
You'll need to create a start, execute and a finish method.
2. the constructor of the batch should have a Set<String> argument so you can send the list of order numbers. Initialize a class variable with it so you can use it in the start() method when extracting the list of records
3. use Database.QueryLocator instead of Iterable when creating the start().
4. reuse your httprequest code from the future method above in the execute(). You'll only need to process 1 record. Put some sort of warning(email etc) when a callout fails...
5. in the trigger you'll call your class with smth like this:
"1" means just 1 record from the list return by the start qerylocator is proccessed at a time by the execute().
Hope this helps and sorry for the late answer, got swamped with mail and work :) ...
Adrian
so, if the callout fails, is there any way to retry ?
Can I Schedule another class every day to capture all the records that failed today and send callouts again ? is this a good option ?
What other options do we have ?
Cheers!
Hello again!
Use a for loop to retry in case of fail. Something like:
You'll need to save somewhere the ids of the records that failed if you want to send the callouts again. Implement stateful for you batch class and keep a list of them. Here's a thing that I'm not sure about: if you can start another batch from a batch class... If you can, then send the list of failed records to it and try to process them again...
You could also save the failed record ids to some helper object, like a custom setting. Your idea with the scheduler that tries to process them daily is pretty good. Be sure to delete them from the list after a certain amount of time, failed tries or successful ones
Happy Friday,
Adrian
Thanks Adrian. The retry mechanism sounds good. I will give it a try and let you know.
5. in the trigger you'll call your class with smth like this:
As mentioned by you in pointer 5, If we initiate a batch class from trigger, it hits the governor limits - too many concurrent batch jobs in this org (limit is 5)! How do we take care of this ? Please share your thoughts.
Cheers!
Barring the fact that you have a bulk efficient trigger and the batch is started correctly with a set of ids, that means you are inserting multiple records at very close times(but different). So there are 5 or more trigger calls which in turn translate in 5 or more batches... :(
How are the Order_Details__c created? Through sfdc? API calls from another system? When did you get the error? Was it for a bulk insert with the data loader by any chance?
Thanks,
Adrian
H Adrian:
Wouldn't;t Manish still be limited by the number of callouts that he could make to Amazon's webservice? I am using batch for GeoCode updates using Google Maps and it only allows 200 callouts per day.
Thanks for any advice.
Tony