Salesforce Apex Custom Id Generator

Published on:
public with sharing class Atomic {

  // Warning!!! This code has not been tested thoroughly. It works, but

  // may throw too many lock failures, etc. It may not work for your

  // particular use case.


  // We had a requirement that both accounts and contacts should have an

  // auto-incrementing id, but the customer never wanted the ids to overlap.

  // We could have just started the counters far enough apart to make sure

  // the max of one would never reach the min of the other, but the 

  // customer didn't like that.


  // The key to the whole thing is SOQL "select for update" syntax.

  // See getAtomicSingleton() below.

  // This will cause an error to be thrown if there are any changes to your

  // record between the time you select and the time you update. This isn't 

  // ideal, but it's the only way I can think of to achieve atomicity.


  // We created a custom object, Atomic__c, that will only ever have a single

  // row and a single field, Account_Contact_Id__c.


  // We will use a trigger to set the account and contact ids. 


  // We select this single row, add the number of IDs we are going to

  // need in our trigger to the current Account_Contact_Id__c value. 

  // Then we update the row with it's new value. If it

  // fails we try again up to 7 times.


  public class MyException extends Exception {}

  // Return an array of integer ids which once returned will ever be returned

  // again. So once 1-10 have been returned they will never be returned again.

  // The smallest id returned next time this method returns successfully will 

  // be 11.

  public static Integer[] nextAccountContactIds(Integer count) {
    if (count < 1 || count > 200) {
      throw new MyException('Count out of range: '
        + count + '. Must be between 1 and 200 (inclusive).');
    }
    return nextAccountContactIds(count, 0);
  }

  private static Integer[] nextAccountContactIds(Integer count, Integer retry) {
    Atomic__c a = getAtomicSingleton();
    Integer startingValue = 1;
    if (a.Account_Contact_Id__c == null) {
      a.Account_Contact_Id__c = startingValue;
    }
    Integer[] rv = buildArray(a.Account_Contact_Id__c.intValue(), count);
    a.Account_Contact_Id__c += count;
    try {
      update a;
    } catch (Exception e) {
      if (retry < 7) {
        return nextAccountContactIds(count, ++retry);
      } else {
        throw e;
      }
    }
    return rv;
  }

  private static Integer[] buildArray(Integer startingNumber, Integer count) {
    Integer[] ints = new List<Integer>();
    for (Integer i = startingNumber; i < startingNumber + count; i++) {
      ints.add(i);
    }
    return ints;
  }

  // The very first time this is called it will create the singleton row. 

  // It will NOT be atomic that very first time because we are doing an insert

  // not a select for update. You may want to call nextAccountContactIds()

  // manually once to seed the table before using it in a context that will

  // require atomicity.

  private static Atomic__c getAtomicSingleton() {
    Atomic__c[] aa = Database.query(
      'select ' +
      ChargentService.allFields(Atomic__c.SObjectType) +
      ' from Atomic__c where Name = \'Singleton\' limit 1 for update');
    if (aa.size() == 0) {
      Atomic__c a = new Atomic__c();
      a.Name = 'Singleton';
      insert a;
      return a;
    }
    return aa[0];
  }
}