0

I am doubtful whether I am going in the right direction. My requirement is to upload all the documents from Salesforce to my local storage space. I have a button that triggers the following function:

  1. Get the list of all the document objects.
  2. Login to the system.
  3. Get the related parameters.
  4. Run parallel uploads through @future(callout=true). This is the part where I'm not sure whether this is the right approach. Please correct me if I'm wrong as future is used for asynchronous web callouts and I thought this may be the correct approach.
  5. Logout only when all the uploads are completed. This is the issue I have encountered with the approach where futures were still running and I got an error when calling logout through HttpRequest. I am not sure where to use Test.startMethod() and Test.stopMethod() as it requires static keyword and no parameters on the method. Plus, a few parameters on the class.

What I have in my mind is that have a VF Page that shows documents, status of upload in the page and update the status of the page simultaneously monitoring the jobs.

This is the class skeleton I have:

public class DocumentObjects{

    class CustomException extends Exception {}

    private static String mainDirName = '<dirName>';
    private static final String USERNAME = '<username>';
    private static final String PASSWORD = '<pwd>';
    private static final String BASEURL = '<url>';
    private List<Document> documentList;
    private Map<String, String> params = null;

    // Constructor to query and display through the page
    public DocumentObjects()
    {
        queryDocument();
    }

    public List<Document> getDocuments() 
    {
        return documentList;
    }    

    private void queryDocument()
    {
        documentList = new List<Document>();        
        for(Document doc: [select Id, Name, Type, Description, ContentType, Body, Url, BodyLength, SystemModstamp from Document])
            documentList.add(doc);                  
    }

    // Button action call
    public PageReference migrate() 
    {
        login();
        setUploadParams();
        startUpload(); 
        //Problematic one!
        //logout();
        return null;
    }

    private void login()
    {
        ....
        Http requests
        ...
        //throw new CustomException() when some error
    }

    private void setUploadParams()
    {
        // Get the parameters from the local server, authentication params, etc.
    }

    ...

    private void startUpload()
    {
        for(Document doc : documentList)
        {
                String fileName = obj.Name + '.' + obj.Type;          
                ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO, 'Uploading ' + fileName));
                sendRequest(url, fileName, obj.BodyLength, obj.SystemModstamp, folderPath, obj.body, token, folderId);
        }
    }

    @future (callout=true)
    private static void sendRequest(String url, String file_name, Integer fileSize, dateTime lastModified, String folderPath, Blob file_body, String token, String folderId)
    {        
        HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse();
        Http http = new Http();

        // Set the parameters

        res = http.send(req);
        Integer statusCode = res.getStatusCode();
        String statusMessage = res.getStatus();

        if(statusCode == 200)
        {
            // Get the response info
            if(errorCode != '0')
            {
                // Get message and throw new exception
                throw new CustomException('Error under upload for ' + file_name +'. Error code: ' + errorCode + '. Error Message: ' + errorMessage);
            }
        }
        else
            throw new CustomException('Error under upload for ' + file_name + '. Status code: ' + statusCode + '. Reason: ' + statusMessage);
    }


    // Problematic code
    private void logout()
    {
        // Request to logout
    }

}

Is this the right way to go? Or are there any other ways where I can make asynchronous callouts and maintain the status?

Thanks.

2

If you haven't already seen it, this Asynchronous Processing in Force.com provides some interesting implementation detail and recommendations.

Some thoughts on your approach:

  • Unless your documents are all small, by querying all the document bodies at once you are likely to exceed the 6M heap space governor limit in the initial request. (I didn't realise that @future allows Blob arguments so good to know that.)
  • If you have many documents, you may exceed the 50 future calls in a request governor limit.
  • The system you are sending to may have limitations on how many parallel requests it can accept: if you can, find out what those limitations are.
  • As the asynchronous requests might not be executed for many minutes, logging in, performing the action and then logging out is probably the only safe approach (assuming the remote system doesn't mind if the same credentials are used in multiple sessions at once).

I wonder how much parallelism is achieved by the @future approach?

Personally, I would write this as a Batchable processing say 50 Documents per batch (login, send 50 Documents, logout) to stay within the 100 web service callouts governor limit. The start method would return a QueryLocator of the Documents. But that may be much slower than your approach.

| improve this answer | |
  • Thanks. Winter '15 release added a new Queueable interface that not only deals with batches but also monitoring them through queries. – dmachop Jul 7 '15 at 20:05

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.