Search

Friday, 18 November 2011

Serialize Batch Apex

In Force.com you cannot call a batch Apex class from another batch Apex class because batch Apex is a future call. However, you can use Database.Stateful and the finish() method to mimic serialization of batch processes.

After batch 1 is complete in the finish() method this calls startNewBatch() in GeneralUtils which fires the newbacth batch class


However, running this you will see an error
Database.executeBatch cannot be called from a batch or future method.


global class batch l implements Database.Batchable<sObject>, Database.Stateful{

global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC,
List<sObject> scope){

}

global void finish(Database.BatchableContext BC){

     GeneralUtils.startNewBatch();
}

}

public class GeneralUtils{

 public static void startNewBatch(){
    newbacth batchable = new newbacth();
    Id
newbacthID = Database.executeBatch(batchable);
}

Tuesday, 4 October 2011

This Is The Strangest Thing Ive Seen In Salesforce

So do we all agree that there's no problem if you want to insert a record on 1 object and after that has inserted correctly, no problem, you
immediately want to insert another record on a different object.

Wrong! You must be joking right. I do this all the time. Well there are certain times when this is not possible
I was making a unit test in a Sandbox and I was populating a custom setting with data. Then immediately after I was trying to insert
an Account record. Now I'd setup my test data I then performed my unit tests and ran the unit test in Eclipse. The unit test passed. Perfect.
Then later that day I was running All Tests in the same sandbox and to my surprise my new unit test failed. I knew there hadn't been any changes to code, meta data, data, or anything
on my sandbox. So after some investigation I came across this article

http://stackoverflow.com/questions/2387475/how-to-avoid-mixed-dml-operation-error-in-salesforce-tests-that-create-users

I also looked at
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects.htm

From this information I wrapped System.runas around the insert statement that created the custom setting record to run this code as System Administrator, as shown below. The result was the unit test passed in both Eclipse and in the browser when I logged into Salesforce.

    Profile systProfileID = [Select id From Profile where Name Like 'System Administrator' LIMIT 1];
    list<User> us1 = [Select id From User where ProfileID=: systProfileID.id and IsActive=true limit 1];
   
    if (us1.size() > 0){
        System.runAs(us1[0]){
            //insert custom setting record
        }
    }

    //now you can safely insert any standard object record


This certainly qualifies as 1 of the most strangest bugs I've come across in Salesforce. Be aware!

Using The Builder Pattern

If you have a class which has specific methods, variables, properties which always need to be set and run and possibly not on the construction of the class, using the Builder Pattern is a very useful way to do this.
The Builder Pattern is a clearer and more efficient way of setting up the environment of a class.

If you were to set and run methods, variables, properties on construction of the class you might as well do this in the class's constructor method.
But if you wanted to set specific environment setting a bit later then builder methods are ideal

Here is the code of a class that uses the Builder Pattern
public class ThisClass{

private String HerName;
private String HerJob;
private String Herotherjob;

public ThisClass setName(String passName){
    HerName=passName;
    return this;
}

public ThisClass setJob(String passJob,String otherjob){
    HerJob=passJob;
    Herotherjob= otherjob;
    return this;
}

public ThisClass setJob(String passJob){
    HerJob=passJob;
    return this;
}

}


This invokes the Builder Pattern class above and uses the Builder Pattern on lines 2 and 3. As you can see all the work was done on just 1 line.
1. ThisClass cls = new ThisClass();

2. cls.setName('Liz').setJob('Secret Agent','Window Cleaner'); //This uses the 1st variety of setJob

3. cls.setName('Liz').setJob('Secret Agent'); //This uses the 2nd variety of setJob

For more information and a useful example on the  Builder Pattern see http://developer.force.com/cookbook/recipe/email-utility-class

Setting Up Salesforce To Salesforce

Note: not all records of objects can be shared using Salesforce To Salesforce

First of all lets get the basics out of the way. Follow the steps in

http://wiki.developerforce.com/index.php/An_Introduction_to_Salesforce_to_Salesforce

Now for some of the things that Salesforce don't tell you, or it is difficult to find information on.

Regarding the manual sharing records using  "Forward to connection" button. Well I've never personally found this button so if anyone does see this anywhere please tell me.


Sharing Accounts and Attachments Using Code
If you want to share attachments make sure that Setup > Security > 'HTML Documents and Attachments Settings' is not ticked otherwise you won't be able to share certain file types.

Attachments need to have a record associated with it, such as an Account record. You need first of all share the Account record with the other Salesforce orgs first before you can share any Attachments. So of course the donor needs to have Account set up in Connection > Published Objects

Here is some code which will allow you to share the Account and then the Attachment.


List<PartnerNetworkConnection> connMap = new List<PartnerNetworkConnection>();
connMap=[select Id, ConnectionStatus, ConnectionName from PartnerNetworkConnection   where ConnectionStatus = 'Accepted'] ;
Account acc =[Select id From Account where id = '<<Account ID>>'];
for(PartnerNetworkConnection network : connMap) {  
     PartnerNetworkRecordConnection newrecord = new PartnerNetworkRecordConnection();
    newrecord.ConnectionId = network.Id;
    newrecord.LocalRecordId = acc.id;
    newrecord.SendClosedTasks = true;
    newrecord.SendOpenTasks = true;
    newrecord.SendEmails = true;  
    insert newrecord;
}

Now that the Account has been shared you can share the Attachment.

Attachment att =[Select id,IsPartnerShared  From Attachment where Parentid='<<Account ID>>'];
att.IsPartnerShared =true;
update att;

If you want to share Attachments manually once the Account has been shared, click edit on the Attachment and a 'Share with Connections' tick box will now be available, tick this and the Attachment will now be shared.

You can setup the recipients to automatically accept shared records. If you do the records will become live automatically in the recipient org, otherwise you need to go to the Account tab, scroll down to  'Accounts from Connections' section, press Go will display any shared Accounts which you can now accept.

Thursday, 1 September 2011

Stopping records being changed other than by Sys Admin Users

This as an example will prevent anyone other than System Administrators to update any Notes

trigger UpdateNotes on Note (before update) {

public class myException extends Exception{}

Profile profileID = [Select id From Profile where Name Like 'System Administrator' LIMIT 1];
list<User> thisUser = [Select id,ProfileID From User where ProfileID !=: profileID.id and IsActive=true and id =:userinfo.getUserId() limit 1];

 if (thisUser.size() > 0){
     //not sys admin
     throw new myException('Cannot update');
 }

}

Batch Apex Processing

This is based on a speech I made to the Salesforce London Group.
This is a brief introduction to batch processing


Batch Requirements

Every batch class requires 4 things. To implement Database.Batchable<sObject>, have a start(),execute() and finish() methods.

global class UpdatetFields implements Database.Batchable<sObject>{

global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC,
List<sObject> scope){

}

global void finish(Database.BatchableContext BC){
}

}


Governor Limits

In total 50 million records can be processed by a batch class and 5 batches can be run simultaneously, giving a total of 250 million records that can be processed at 1 time.


Remember batch classes still need to adhere to governor limits, so if any soql’s are performed do remember that these soql’s need to adhere to governor limits

The following is likely to break governor limits because List<sObject> scope is likely to contain more than 200 records and so the soql in bold will run 200 times per batch that is processed.

global void execute(Database.BatchableContext BC,
List<sObject> scope){

ID thisCustID;
List<Customers__c> allsubs;

for (SObject sub: scope){
     thisCustID = (ID)sub.get(‘Customers__c');
     allsubs = [Select Name From Customers__c where id =: thisCustID];
}

}

Types of Batch Classes

Using Database.QueryLocator this passes an list of SObjects. This is the most common type used in batch processing. Or you can use Iterable which allows you to step through the records more easily, which is mainly used for more complex soql’s such as nested soql’s.

global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}

Or

global Iterable start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}


And


If your batch performs a callout you must implement Database.AllowsCallouts

global class SearchAndReplace implements Database.Batchable<sObject>,
Database.AllowsCallouts{
}


And


If you specify Database.Stateful in the class definition, you can maintain state across these transactions. This is can be used for example if you want to make some kind of summary, or total of the records processed, or could be used to summarise all the successful or failure transactions produced etc.

global class UpdatetFields implements Database.Batchable<sObject>, Database.Stateful{

}


Ensure Batches Don’t Try To Update The Same Records

Use AsyncApexJob to query when a batch has completed processing so you can run the next batch

ID batchprocessid = Database.executeBatch(reassign);
AsyncApexJob aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
FROM AsyncApexJob WHERE ID =: batchprocessid ];

If (aaj.Status == ‘Completed’){
      //run next batch
}
Else { }

Custom Schedule Classes

If you ever have to shceduled a class but provide a specific custom shcedule plan run a bit of code in the System Log, similar to

string CRON_EXP = '0 30 * ? * MON-FRI';
string jobid=System.Schedule('<<any name>>',CRON_EXP, new <<name of class>>());

This will run the class every 30 mins passed the hour from Mon-Fri

Changing the 1st line with
CRON_EXP = '0 0 * ? * MON-FRI';

will run the class on the hour 

More info for specific customisation at     http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_system.htm