Thursday, April 30, 2015

A Web Service CallOut Per Minute In Between Business Hours

There was a very specific requirement from customer, as per the requirement he wanted to call a external websrvice in every minute during the business hours. I tried several ways to do it but that was really not simple. Even I was not sure it can hit any governor limit. I did google a lot for this but there was no help in case of per minute. Finally started to get if there is any solution in salesforce for this, without having confidence is this really possible in salesforce. But finally I got the solution & the best thing is it works without hitting any kind of governor limits. Here is the solution which can help if you stuck this kind of requirement.

#1-Create a new object called “Scheduled_Job__c”. This object will have below fields –
Request_DateTime__c – Type DateTime
Response__c – Type Long Text Area(32768)

This object will be used in two ways latter-
#A-track the request time &
#B-What is the response from the external web service.
#2-I created a batch class which allows callouts.
global class ScheduleLeads implements Schedulable,Database.AllowsCallouts {
 global void execute(SchedulableContext ctx) { 
      init(); 
 }
}

#3-Created a method in this class called ScheduleJob which will basically can be called from execute anonymous to schedule this job.
public static void ScheduleJob(){
 boolean isNeedToSchedule = true;
 String strSchedule = '';
/*here notice that we are adding a minute in current time, means it will be scheduled for next minute, you can change it even if you want to call every n minutes*/
 DateTime dt = Datetime.now().addMinutes(1);
/*Let’s say business hours is from 10 AM to 8 PM*/
 if(dt.hour() >=19 && dt.minute() >= 59){ 
         isNeedToSchedule = false;
 }else{
     strSchedule = '0 ' + dt.minute() + ' ' + dt.hour() + ' ' + dt.day() + ' ' + dt.month() + ' ?' + ' ' + dt.year(); 
     isNeedToSchedule = true;
 }
 if(isNeedToSchedule){
   String cronid = System.schedule('CalloutService_' + Datetime.now().getTime(), strSchedule, new ScheduleVWLeads());
  
   List listOfScheduledJob = [Select Id,Response__c from Scheduled_Job__c Order by CreatedDate DESC LIMIT 1];
   if(listOfScheduledJob != NULL && !listOfScheduledJob.isEmpty()){
    /*try{ 
     delete listOfScheduledJob;
    }catch(Exception ex){
     system.debug('exqqq::--->>>>' + ex);
    }*/
   }
   Scheduled_Job__c objScheduledJob = new Scheduled_Job__c();
   objScheduledJob.Name = cronid;
   objScheduledJob.Request_DateTime__c = datetime.now();
   insert objScheduledJob;
 }      
}

#4-Create another method called init
@future(callout=true)
  public static void init(){
     HttpRequest req = new HttpRequest();
  Http http = new Http(); 
  req.setMethod('GET');
  String requrl = 'YOUR_WEBSERVICE_URL'+ '?type=' + XYZ;
  req.setEndPoint(requrl);
  HTTPResponse res;
  if(!Test.isRunningTest()){
   Blob headerValue = Blob.valueOf('YOUR_WEBSERVICE_USERID' +':' +'YOUR_WEBSERVICE_PASSWORD');
   String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
   req.setHeader('Authorization', authorizationHeader);
   req.setTimeout(120000);
   res = http.send(req);
  }else{
   res = new HttpResponse();
   res.setHeader('Content-Type', 'application/json');
   res.setBody('DUMMY_RESPONSE_IN_CASE_OF_TEST_EXECUTES');
   res.setStatusCode(200);
   res.setStatus('Ok');
  } 
  String resBody;
  if(res != NULL){
   resBody = res.getBody();
   JSONParser parser = JSON.createParser(resBody);
   while (parser.nextToken() != null) {
    /*PROCESS YOUR RESPONSE HEREE */
   }
   ScheduleJob();
  }
  
 }

Here one important point is we are calling the schedulejob method in init again so this is scheduled for next minute.
Now if you start this job at some particular time daily let’s say when business start 10 AM in morning, go to schedule job & schedule a job by using this class. That’s it. The job will start on regular time daily & in code we are having a condition when it will stop. So in this way the job will execute from 10 AM to 8 PM daily.
You can either keep ScheduleJob records for tracking or can delete if not needed. In the above code there is a piece of code which is commented & that is deleting the ScheduleJob object records.

Happy ending! :)

Avoid simultaneous update/ Control same record if already opened by someone in edit mode & show a message to end user

Recently I came across a requirement where client wanted to track if any other user also open the same record to edit, in this case he wanted to show a message to second user so he can edit the same record latter.

To solve this I created a custom setting let’s say Control_Record_Edit__c. Created few other fields to track edit mode –

Name
Edit_DateTime__c
User_Id__c

Name:- field will save record id which is opened in edit mode by someone.
Edit_DateTime__c:- field will tell us how long someone open a record in edit mode
User_Id__c:- field will tell us who open that record in edit mode.


Now let’s say we want to track lead record. We will create a new VF page that will be called LeadEdit.page. In this page we will have a function called init which will basically insert a record in our custom setting, so next time whenever someone open the same lead it edit mode we can check in code & show a message that another user is already opened this lead in edit mode.

<apex:page standardController="lead" extensions="LeadEditController" action="{!init}">
 <apex:form id="form1">
  <apex:outputPanel rendered="{!isNeedToShowMsg}">
   <apex:pageMessages id="error"/>
   <apex:commandButton value="Go To Lead" action="/{!objLead.Id}" style="margin-left: 20px;"/>
  </apex:outputPanel>
 </apex:form>  
</apex:page>


Class code is as below
public with sharing class LeadEditController {
 public Lead objLead {get;set;} 
 public boolean isNeedToShowMsg {get;set;}
 public LeadEditController(ApexPages.StandardController stdController){
  objLead = (Lead)stdController.getRecord();
 }
  
 public PageReference init(){
  isNeedToShowMsg = false;
  PageReference pr = NULL;
  List listOfControlLeadUpdate = [SELECT Id,User_Id__c,Edit_DateTime__c from Control_Lead_Update__c where Name=:objLead.Id and User_Id__c !=:Userinfo.getUserId() LIMIT 1];
  if(listOfControlLeadUpdate != NULL && !listOfControlLeadUpdate.isEmpty()){
   List listOfUser = [Select Id, Name from User where Id=:listOfControlLeadUpdate.get(0).User_Id__c LIMIT 1];
   if(listOfUser != NULL && !listOfUser.isEmpty()){
    String Msg = 'Another User ' + listOfUser.get(0).Name + ' already start editing the same lead at '+  listOfControlLeadUpdate.get(0).Edit_DateTime__c.format()  +', Please wait for sometime & click on edit button again!'; 
    ApexPages.Message apexmsg = new ApexPages.Message(ApexPages.Severity.INFO, Msg);
             ApexPages.addMessage(apexmsg);
             isNeedToShowMsg = true;
   } 
   
  }else{
   Control_Lead_Update__c objControlLead = new Control_Lead_Update__c();
   objControlLead.Name = objLead.Id;
   objControlLead.Edit_DateTime__c = datetime.now();
   objControlLead.User_Id__c = Userinfo.getUserId();
   try{
    insert objControlLead;
   }catch(Exception ex){
    system.debug('exception::-->>>' + ex);
   }
   pr = new PageReference('/' + objLead.Id + '/e?nooverride=1&retURL=/apex/LeadCancel?Id=' + objLead.Id);
  } 
  return pr; 
 }
 
 public PageReference initCancel(){
  LeadHandler.ReleaseEditModeForOthers(new List{objLead});
  PageReference pr = new PageReference('/' + objLead.Id);
  return pr;
 }
}



Here after this one other issue appear when user click on cancel, if user open a lead & click on cancel we should remove his record from custom setting but the problem is cancel override is not available in salesforce. To handle this we created another page that is called LeadCancel & we are passing it in the retURL parameter.

<apex:page standardController="lead" extensions="LeadEditController" action="{!initCancel}">

</apex:page>

now in the class code there is a method initCancel, that is basically deleting the record from custom setting if a user open a lead & click on cancel button. also we are passing this cancel page in retuURL parameter so when user click on cancel it will be back to our page.

There is only one point here which is important, if a user open a lead & close it by browser close button then we can not track that action & in that case no one can edit the same lead until that record does not removed from custom setting.