2

I am able to do callout using apex batch with user interaction (on demand). So when user click the button, this apex batch will run without any problem

calling the apex batch:

global class InvoiceHeaderListController {
    public static HttpCalloutMock mock{get;set;}
    public ResponseData rData{get;set;}
    public String lastNum{get;set;}
    public Boolean hasNext{get;set;}

    private static final String ENDPOINT_HEADER = Label.BP_Invoice_Header;
    private static final String API_KEY = Label.BP_Api_Key;

    public void doSearch() {
        lastNum = '0';
        hasNext = false;

        getInvoiceHeader(lastNum);
        List<InvoiceData> invoiceDataList = getInvoiceHeaderRows(rData);
        do {
            getInvoiceHeader(lastNum);
            List<InvoiceData> anotherList = getInvoiceHeaderRows(rData);
            invoiceDataList.addAll(anotherList);
        }
        while(hasNext == true);

        //call the apex batch
        InvoiceHeaderBatch ihb = new InvoiceHeaderBatch(invoiceDataList);
        Database.executeBatch(ihb, 100);
    }

    public HttpResponse getInvoiceHeader(String lastRowNum) {
        Http httpObj = new Http();
        HttpRequest requestObj = new HttpRequest();
        HttpResponse responseObj = null;

        String reqEndpoint = ENDPOINT_HEADER + '?';
        reqEndpoint = reqEndpoint + 'apiKey=' + API_KEY;
        reqEndpoint = reqEndpoint + '&' + 'lastRowNum=' + (String.isNotBlank(lastRowNum) ? lastRowNum : '0');

        requestObj.setEndpoint(reqEndpoint);
        requestObj.setMethod('GET');
        requestObj.setHeader('Content-Type', 'application/json');
        if (Test.isRunningTest() && mock != null) {
            responseObj = mock.respond(requestObj);
        }
        else {
            responseObj = httpObj.send(requestObj);
        }

        if (responseObj.getStatusCode() != 200) {
            System.debug(LoggingLevel.DEBUG, '***The status code returned was not expected: ' + responseObj.getStatusCode() + ' ' + responseObj.getStatus());
        }
        else {
            String responseString = responseObj.getBody();
            rData = (InvoiceHeaderListController.ResponseData) JSON.deserialize(responseString, InvoiceHeaderListController.ResponseData.class);
        }

        return responseObj;
    }

    public List<InvoiceData> getInvoiceHeaderRows(ResponseData rd) {
        List<InvoiceData> resultList = rd.receivableInvoices;
    }

    public class ResponseData {
        public List<InvoiceData> receivableInvoices{get;set;}
        public String lastRowNum{get;set;}
        public Boolean hasNext{get;set;}
    }

    public class InvoiceData {
        String invoicenumber{get;set;}
        String invoicestatus{get;set;}
    }
}

apex batch class:

global class InvoiceHeaderBatch implements Database.Batchable<InvoiceHeaderListController.InvoiceData>, Database.Stateful, Database.AllowsCallouts {
    global List<InvoiceHeaderListController.InvoiceData> invoiceDataList = new List<InvoiceHeaderListController.InvoiceData>();
    global Map<String, Invoice__c> invoiceMapToUpsert = new Map<String, Invoice__c>();
    global Integer recordsProcessed = 0;

    global InvoiceHeaderBatch(List<InvoiceHeaderListController.InvoiceData> invDataList) {
        this.invoiceDataList = invDataList;
    }

    global System.Iterable<InvoiceHeaderListController.InvoiceData> start(Database.BatchableContext bc) {
        return this.invoiceDataList;
    }

    global void execute(Database.BatchableContext bc, List<InvoiceHeaderListController.InvoiceData> scope) {
        for (InvoiceHeaderListController.InvoiceData iData : scope) {
            //do stuff
            recordsProcessed = recordsProcessed + 1;
        }
    }

    global void finish(Database.BatchableContext bc) {
        System.debug(LoggingLevel.DEBUG, '***TOTAL: ' + recordsProcessed);
    }
}

I tried to schedule the apex batch to do the callout apex batch daily:

global class InvoiceHeaderListSchedule implements Schedulable {
    global void execute(SchedulableContext sc) {
        InvoiceHeaderListController.doSearch();
    }
}

When the apex batch run, I got the following error:

System.CalloutException: Callout from scheduled Apex not supported.

Is there any workaround to run apex batch callout with schedule?

EDIT

I am updating the callout to run in future:

@future(callout=true)
public static void doSearch() {
    ...
}

and got error:

System.AsyncException: Database.executeBatch cannot be called from a batch start, batch execute, or future method.

I ran out of ideas about how to schedule callout apex batch. Is this impossible thing to do?

5

You need to move the callout logic to the batchable class' start method (as opposed to passing in records in the constructor). From there, you can then proceed as normal. Without trying to deconstruct all this, that generally means:

global class InvoiceHeaderBatch implements ... {
  global InvoiceData[] start(Database.BatchableContext context) {
    // This line here would do all the callouts done from doSearch
    InvoiceData[] values = getInvoiceData();
    return values;
  }
}

Your scheduled class would then become:

global class InvoiceHeaderListSchedule implements Schedulable {
    global void execute(SchedulableContext sc) {
        Database.executeBatch(new InvoiceHeaderBatch(), 100);
    }
}

And even your doSearch method would become simply:

public void doSearch() {
    Database.executeBatch(new InvoiceHeaderBatch(), 100);
}

In other words, your callout logic is in the wrong place. It should exist either as part of the batch class, or a special utility class you can call, not contained in the controller you placed it in originally.

|improve this answer|||||
  • I see, so the callout need to be placed in start method apex batch class – Rival Jul 25 '18 at 7:25

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.