(1) To determine the number of Future calls run in the past 24 hours, you can use this utility method:
public static Integer GetNumFutureCallsInLast24Hours() {
return [select count() from AsyncApexJob
where CreatedDate >= :Datetime.now().addHours(-24)
and JobType = 'Future'];
(2) To dynamically run methods in a future
context ONLY if you have future
calls available, you could leverage a "dispatcher" method framework that would run your code in a future
context only if you have not surpassed the 24-hour-calls Limit and Call Stack Limit yet:
// Can we run future calls right now?
public static boolean CanUseFutureContext() {
boolean callStackLimitExceeded = Limits.getFutureCalls() >= Limits.getLimitFutureCalls());
if (!callStackLimitExceeded) {
// Check 24-hour rolling window limit
// of 200 * # of Salesforce, Salesforce Platform, or Force.com One App Licenses
// *NOTE 1*: These queries may be cacheable depending on your situation
// *NOTE 2*: If you have not used up all of your allocated licenses,
// you will have more available licenses than this, so you may need to adjust.
// However, you can't use SOQL to determine
//how many AVAILABLE licenses you have,
// only how many have actually been assigned to users
return GetNumFutureCallsInLast24Hours() < getMaxFutureCallsAllowed();
} else return false;
}
public static Integer getMaxFutureCallsAllowed() {
Integer usersCount = [
SELECT
COUNT()
FROM
User
WHERE
Profile.UserLicense.LicenseDefinitionKey IN ('SFDC','AUL','PID_FDC_FREE')];
return Math.max(250000, usersCount*200);
}
// Main entry point to your method: call this from a trigger, other class, etc.
public static void RunMyLogic(params) {
if (CanUseFutureContext()) futureRunMyLogic(params);
else RunMyLogicMain(params);
}
private static void RunMyLogicMain(params) {
/* Your logic here */
}
@future
private static void futureRunMyLogic(params) {
RunMyLogicMain(params);
}
Another good way to avoid recursion is highlighted in Chapter 6 of Dan Applemans's excellent book, Advanced Apex Programming. This particular example is around recursion when looking for a specific field change which is very common in many triggers.
As mentioned in @BarCotters answer, you generally run your trigger on very specific criteria. A change in a field can trigger certain logic in your trigger. If you look at the Order of Operations for triggers, you can see that any workflow rules and field updates based on those rules are run after the both before and after triggers have run. It runs however prior to the changes actually being committed to the DB, which is where this can cause problems when detecting field changes.
So lets say we are using a very simple example where we have a trigger on the Opportunity object that looks for opportunities that have been Won, and based on that it creates a new custom object record.
trigger OpportunityTrigger on Opportunity (after update) {
map<Id,Opportunity> justWonOppsMap = new map<Id,Opportunity>();
for (Opportunity o : Trigger.new) {
if (o.isWon != Trigger.oldMap.get(o.Id).isWon) {
justWonOppsMap.put(o.Id, o);
}
}
List<Some_Custom_Object__c> objs = new list<Some_Custom_Object__c>();
for(Opportunity o : justWonOppsMap.values()){
objs.add(new Some_Custom_Object__c(
Name = 'New object based off ' + o.Name,
Opportunity__c = o.Id
));
}
insert objs;
}
This trigger will work fine, and will create a new custom object when you close an Opportunity. It will work fine that is, assuming you have no WFR's that have field updates on the Opportunity Object.
Let's say now I had a workflow rule on the Opportunity that When an Opp is closed, it changes the CloseDate of the Opportunity to Today. (A fairly simple and common WFR that many admins may add).
That simple change now Breaks my trigger. This same trigger will now actually create 2 custom objects when my opportunity is closed. This is due to the fact that the WFR rule is now firing the triggers one final time.
This is how you would think the logic works
- Trigger Run 1 (Old Value = Not Won, New Value = Won)
- Workflow Trigger
- Run 2 (Old Value = Won, New Value = Won)
This is not how it works though, even though the opp has been updated, it has not yet been committed to the Database, so in the second run of the trigger it still see's the opp and just being closed like below
This is actually how it works
- Trigger Run 1 (Old Value = Not Won, New Value = Won)
- Workflow
- Trigger Run 2 (Old Value = Not Won, New Value = Won) Identical as first run
This is where Dan Appleman's solution comes in. He advises to use a mechanism that actually checks for the 'correct old value'. This would allow the second run of the trigger to detect the value that was set in the first run of the trigger.
trigger OpportunityTrigger on Opportunity (after update) {
OpportunityTriggerHelper.OppAfterUpdate(trigger.ew, trigger.old, trigger.newMap, trigger.oldMap);
}
public class OpportunityTriggerHelper {
Private static Map <Id,boolean> oldIsWonMap = null;
public static void OppAfterUpdate(list<Opportunity> newOpps, list<Opportunity> oldOpps, map<Id,Opportunity> newMap, map<Id,Opportunity> oldMap) {
if(oldIsWonMap == null) {
oldIsWonMap = new map<Id,boolean >();
}
map<Id,Opportunity> justWonOppsMap = new map<Id,Opportunity>();
for (Opportunity o : Trigger.new) {
//This checks to see if there was a value set in a previous trigger run
boolean oldIsWon = (oldIsWonMap.containsKey(o.id)) ? oldIsWonMap.get(o.id) : oldmap.get(o.id).isWon;
//this checks the current opp value with the 'correct' old value
if(o.isWon && !oldIsWon){
justWonOppsMap.put(o.Id, o);
}
//this puts in the 'correct' old value in case the trigger is run again in the same context
if(oldIsWon != o.isWon) {
oldIsWonMap.put(o.id,o.isWon);
}
}
List<Some_Custom_Object__c> objs = new list<Some_Custom_Object__c>();
for(Opportunity o : justWonOppsMap.values()){
objs.add(new Some_Custom_Object__c(
Name = 'New object based off ' + o.Name,
Opportunity__c = o.Id
));
}
insert objs;
}
}
With this change, now the WFR rule does not break our trigger. Only 1 custom object is created when the opp is closed.
I know this was longwinded, but I hope it helps. I would highly getting this book as this was only a few pages worth of goodness, and this book is jam packed with great knowledge that I think all SFDC developers should have.
Heres the link again.
http://advancedapex.com/
Best Answer
You can always use the
isFuture()
method fromSystem
class instead of a variable which returnstrue
if the currently executing code is invoked by code contained in a method annotated with future; false otherwise. LinkAlso make sure that the web-service is not called from anywhere else.