7

I have my below batch code and i need to schedule it daily at 12.00 AM. The batch runs but nothing gets done due to the session ID problem. How to solve this problem.

public class updateCurrencyBatch ...
{
    private final String sessionId;

    public updateCurrencyBatch(String sessionId)
    {
        this.sessionId = sessionId;
    }

    ...

    public void execute(Database.BatchableContext BC, List<CurrencyType> Scope) 
    {
       ...
       req.setHeader('Authorization', 'OAuth ' + sessionId);
       ...
    }
}

UPDATE 12/26/13 Now I have tested it further and the results are here:

  1. firstly from scheduler class execute method i am calling the batch class constructor which in turns calls the class you given to get sessionID. This gives the error as Scheduled class cannot call the callouts error.
  2. Secondly i tried below code in Scheduler class:

    global void execute(SchedulableContext sc) {
        doSynch();
    }
    
    @future (callout=true) //code that does HTTP callouts
    static void doSynch(){
        updateCurrencyBatch ucb = new updateCurrencyBatch(Some String);
        ID batchprocessid =database.executeBatch(ucb,1);
    }
    

    This gives the error that "Database.executeBatch cannot be called from a batch start, batch execute, or future method.". Its like a dead lock situation.

  • @Andrew Fawcett: Any idea on this – sunny Dec 20 '13 at 7:53
  • there is a good old post about the same in stackexchange salesforce.stackexchange.com/questions/21435/… – Avidev9 Dec 20 '13 at 8:10
  • @Avidev9 That does not help me because i schedule the batch lets say 12.00 am in morning. At that time i will not be login into my org. So the above link code do not work right. – sunny Dec 20 '13 at 11:03
  • 2
    It actually takes your session id at the point you schedule the job, at which point you are logged in. If you don't log out, and your session is somehow kept alive you will be fine. Probably not advisable though for longer periods. – Phil Hawthorn Dec 20 '13 at 15:28
8

You can perform a login from Apex by using the SOAP API login method to retrieve the Session Id. You will need to add a Remote Site setting in your org and possibly append the users security token to the end of the password for this to work.

public with sharing class Login {

    private static final String NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
    private static final String NS_SF = 'urn:partner.soap.sforce.com';

    public static String login(String user, String password)
    {
        // As per http://wiki.developerforce.com/page/Enterprise_Login
        HttpRequest req = new HttpRequest();
        req.setMethod('POST');   
        req.setTimeout(60000);
        req.setEndpoint('https://www.salesforce.com/services/Soap/u/29.0');
        req.setHeader('Content-Type', 'text/xml;charset=UTF-8');        
        req.setHeader('SOAPAction', '""');
        req.setBody('<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"><Header/><Body><login xmlns="urn:partner.soap.sforce.com"><username>' +user+ '</username><password>' + password + '</password></login></Body></Envelope>');        
        HttpResponse res =  new Http().send(req);
        if(res.getStatusCode() != 200)
        {
            Dom.Document responseDocument = res.getBodyDocument();
            Dom.Xmlnode rootElm = responseDocument.getRootElement(); // soapenv:Envelope
            Dom.Xmlnode bodyElm = rootElm.getChildElement('Body', NS_SOAP); // soapenv:Body 
            Dom.Xmlnode faultElm = bodyElm.getChildElement('Fault', NS_SOAP); // soapenv:Fault
            Dom.Xmlnode faultStringElm = faultElm.getChildElement('faultstring', null); // faultstring          
            throw new LoginException(faultStringElm.getText()); 
        }
        // As per http://wiki.developerforce.com/page/Enterprise_Login
        Dom.Document responseDocument = res.getBodyDocument();
        Dom.Xmlnode rootElm = responseDocument.getRootElement(); // soapenv:Envelope
        Dom.Xmlnode bodyElm = rootElm.getChildElement('Body', NS_SOAP); // soapenv:Body 
        Dom.Xmlnode loginResponseElm = bodyElm.getChildElement('loginResponse', NS_SF); // loginResponse
        Dom.Xmlnode resultElm = loginResponseElm.getChildElement('result', NS_SF); // result
        Dom.Xmlnode sessionIdElm = resultElm.getChildElement('sessionId', NS_SF); // sessionId
        return sessionIdElm.getText();
    }

    public class LoginException extends Exception {}
}    

Note on Security: It is of course not good to hard code your user name and password, ideally read it from some object and/or fields only visible to the user running the job (or if its enabled an Encrypted Field). Your other option might be to manually schedule the job via the developer console in this case passing in the user and password to the Scheduled class via its constructor (though i've not tested this as yet).

  • This should give you the general idea, i'd like to complete the example with using XML Document handling (res.getBodyDocument()) to obtain the Session Id and perhaps do some error handling... – Andrew Fawcett Dec 20 '13 at 13:44
  • I've updated the code to retrieve the SessionId and also do some error handling for invalid logins. Enjoy! – Andrew Fawcett Dec 20 '13 at 16:31
  • I tried the above code. I have added the "salesforce.com/services/Soap/u/29.0" to remote sites. Next i am calling the login method by passing the username and password(password+security token) from the constructor of batch class to get session id. But still the scheduled class is not working. – sunny Dec 26 '13 at 5:48
  • Question: @AndrewFawcett if you're going to the trouble of doing this would it not be easier/simpler to use the OAuth Username-Password flow? – Phil Hawthorn Dec 26 '13 at 10:46
  • Yeah possibly on the face of it, i've not really compared the two, wondering if the SOAP variant gives feedback / info if needed? I'll perhaps take a look someday and feedback here and/or a blog. – Andrew Fawcett Dec 26 '13 at 11:01
6

You may find you need to refactor your code to do the callouts etc. but you can use the OAuth Username-Password flow as described here to obtain a Session ID.

First, you must setup a Connected App (Develop->Remote Access->Connected Apps) which will give you the client_id and client_secret.

Connected App

You must also create a Remote Site to allow you callout access to https://login.salesforce.com.

enter image description here

Then, the code is fairly simple (replace values between [] with real values):

HttpRequest req = new HttpRequest();
req.setEndpoint('https://login.salesforce.com/services/oauth2/token');
req.setMethod('POST');
req.setHeader('Content-Type','application/x-www-form-urlencoded');
req.setBody('grant_type=password&client_id=[your client id from connected apps]&client_secret=[your client secret from connected apps]&username=[your sf username]&password=[your user password]');

Http http = new Http();
HttpResponse response = http.send(req);    

Map<String,Object> mapBody = (Map<String,Object>)JSON.deserializeUntyped(response.getBody());

// get the Session ID from the response from the call to oauth2/token
String sessionId = (String)mapBody.get('access_token');

Once you have your Session Id you can then use it as you intended to in your code above.

  • Finally to get sessionId we are passing a string 'access_token', what exactly we need to pass over there. – sunny Dec 31 '13 at 3:59
  • I'm not clear from your comment @sunny whether this answers your question or not. If it does, can you accept the answer. That will help others who also have the issue see what your chosen solution was. – Phil Hawthorn Dec 31 '13 at 11:41
  • When i use your code and call that from a scheduled class. I get an error saying that you cannot make callouts from scheduled class execute method. So i use a future method in scheduled class to make the callouts. That does not give eny error but not getting the sessionID. – sunny Dec 31 '13 at 12:37
  • Why don't you just do two callouts from within your batch apex execute method? First one to get the session id, second one to the service you want to call? – Phil Hawthorn Dec 31 '13 at 13:55
  • In response i am getting this error: {"error_description":"client identifier invalid","error":"invalid_client_id"} and response as System.HttpResponse[Status=Bad Request, StatusCode=400]. I have just used the clientID which i got from the Connected App. – sunny Jan 1 '14 at 9:14
0

You can call future methods from a scheduled class if you architect it properly:

I have posted the code here: Calling future method from Batch

Use this if it will help you.

To directly answer your question you can also use oAuth to get a refresh token, store it in a custom setting, and use that value to get a new access token when the batch runs.

The goto resource for this is: https://developer.salesforce.com/page/Digging_Deeper_into_OAuth_2.0_on_Force.com

-1

I am trying to call a method that i wrote to retrieve the session Id like yours from the constructor of a schedulable class but the method is never getting called. Below is the snippet of the code i wrote. Can you please suggest where I am going wrong?

public class SQRDailyReport_Schedulable implements Schedulable {
private String sessionId;
private static final String NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
private static final String NS_SF = 'urn:partner.soap.sforce.com';

//global SQRDailyReport_Schedulable (String user, String password) {
public SQRDailyReport_Schedulable () {

 sessionId = retrieveSessionId('User', 'pwdSctoken');
 this.sessionId = SessionId;
}

public void execute(SchedulableContext SC) {
SQRCall.sendVF(sessionId);

}
public static string retrieveSessionId(String user, String password) {

    HttpRequest req = new HttpRequest();
    req.setMethod('POST');   
    req.setTimeout(60000);
...
    }
public class LoginException extends Exception {}
}
  • Debarshi -- this should be posted as a new question (with appropriate context) as it is not an answer to the original post – cropredy Oct 2 '14 at 1:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.