function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Sean Parker 11Sean Parker 11 

Callout loop not allowed error when using getContent and getContentAsPDF

Hi there

Salesforce does not allow you to create PDF attachments in batch job.

This is work around to that.
https://developer.salesforce.com/forums/?id=906F0000000AlGoIAK

It works!

But As of Summer 15, Salesforce have implemented a critical update "PageReference getContent() and getContentAsPDF() Methods Treated as Callouts". Once enabled, you may get the rather uninformative error "(304497466)|FATAL_ERROR|System.CalloutException: Callout loop not allowed".

And so I have the problem described in this blog: http://codrspace.com/gwickman/callout-loop-not-allowed-error-when-using-getcontent-and-getcontentaspdf/

Can someone please help me get around this error or help with another way to create PDF attachments for emails in a batch job?

Thank you very much


 
AbdelhakimAbdelhakim
Hi,
You need to create a WS

User-added image

Add session to the constructor
global myBatch(String idsession)
    {              
        this.sessionid = idsession;         
    }

And call the function in your Batch class 'execute function'
HttpRequest req = new HttpRequest();
                    req.setEndpoint( this.addr );
                    
                    req.setMethod('POST');
                    req.setHeader('Authorization', 'OAuth ' + this.sessionid);
                    req.setHeader('Content-Type','application/json');     
                    req.setTimeout(120000);
                    Map<String,String> postBody = new Map<String,String>();
                    postBody.put('idvar',id);             
                    String reqBody = JSON.serialize(postBody);
                    
                    req.setBody(reqBody);
                    Http http = new Http();
                    HttpResponse res = http.send(req);
                    if(res.getStatusCode()==200)
                    {
                        resjson = res.getBody();
                        system.debug('resjson : '+resjson);
                   }
And call your batch :
myBatch myB = new myBatch(UserInfo.getSessionId());
i hope it solve the problem.

 
Sean Parker 11Sean Parker 11
Hi Abdelhakim

Thanks very much!

Can you exlain how it will solve this problem -

"As of Summer 15, Salesforce have implemented a critical update "PageReference getContent() and getContentAsPDF() Methods Treated as Callouts". Once enabled, you may get the rather uninformative error "(304497466)|FATAL_ERROR|System.CalloutException: Callout loop not allowed". 

Many thanks

Sean
 
Dan VegaDan Vega
I am also looking for a solution to this new limit. My current code uses the "batch -> webservice -> pdf generation of VF page & attach to record" pattern.  Now I get the callout loop error if I activate the critical update.  Has anyone found a solution/workaround for this case?
Murray McDonaldMurray McDonald
My current code also uses batch->SOAP->pagereference.getContent() and it encounters the "dml loop" issue with the critical update enabled.  I changed the SOAP to a REST-based call as per the suggestion above but still get the dml loop.  BTW once can successfully use getContent() from a batch job with the critical update enabled.  However becuase you don't have a session you can't hit a VF page that requires authentication.  Still looking for a workaround.
Dan VegaDan Vega
The comment above (Murray McDonald) gave me hope tha once the critical update was enabled that I could move the getContent() call to my batch job.  Unfortunately, I get similar behavior before and after the ciritical update is enabled on that front, a corrupted PDF.  My current solution (pre critical update) works and requires using authentication as well.  Still searching for a solution/workaround.
Camille VersteegCamille Versteeg
Waiting for a solution...
narendra Varma 5narendra Varma 5
I am also facing a similar roadblock.Waiting for solution.
Mitesh SuraMitesh Sura
Add me to waiting list as well.. My flow Trigger >> @Future (CallOut=true) >> REST (HTTP Post)
Sean ParkerSean Parker
The only solution that I can find so far is not to use REST but to replace the getContent() (ie  a call to a visualforce page) with Blob.PDF() :

eg.

Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();

//add other variables (subject etc)

          String   body='<html> <body> </body> </html>'
           Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
            efa.setFileName('filname.pdf');
            efa.Body = Blob.toPDF(body);
         
           email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa});


So you build up your string using html and then set as a Blob and then add that as an attachment to your email in Apex. This will then create a pdf attachment that you can use in a trigger or Batch job. It works for me! The only issue is that you need to spend a lot more time on formatting as certain CSS does not come through!



 
280z280z
I have a similar issue to what SFDC Hedgehog has in https://developer.salesforce.com/forums/?id=906F0000000BNdJIAW  where I'm using getContent() on a report to get the info of the report and sent it as a CSV file attachment to a user.  So Blob.toPDF(body) won't work.  I've been trying to reimplement this getContent() as an HttpRequest to the url of the report in question and returning the body of the response, but I have yet to figure out how to make this work. 
Sean ParkerSean Parker
Hi 280z
I had to rebuild all the filters etc from my report using SOQL in apex and then build the body that way - ie using loops etc. It is a bit tedious but it works.
AbdelhakimAbdelhakim
Hi,
the issue will be solved ine the next release "Winter 16"
http://releasenotes.docs.salesforce.com/en-us/winter16/release-notes/rn_apex_pagereference_getcontent.htm#rn_apex_pagereference_getcontent
 
Steve Berley [Left Propeller]Steve Berley [Left Propeller]
My sandbox is on Winter '16 and I'm successfully doing getContentAsPDF() calls with a method marked @future (callout=true)

However, the sandbox also includes a critical update called:  PageReference getContent() and getContentAsPDF() Methods Treated as Callouts
When I activate it the getContent breaks.

So:  Is the update detritis from Summer?  Could it be thier "fix"?  Should it work after activating the update (implying I'm doing something wrong)?

What are your thoughts?
Murray McDonaldMurray McDonald
I've been running a Winter-16 pre-release Org for the last couple of weeks to test code changes necessitated by the critical update.  We used to use a SOAP callout from a batch job to gt around the problem of not having a valid user session context in a batch job.  The new code eliminates the SOAP callout and uses getContent() directly.

Prior to Winter 16 PageReference.getContent() would not work from asynchronous contexts (batch, @future, scheduled jobs).

One thing I did notice is that the critical update was activated by default in the pre-release Org.

Are you sure you weren't deactivating the critical update?  

How exactly did the getContent() break?  "Callout loop not allowed" or something else?
Steve Berley [Left Propeller]Steve Berley [Left Propeller]
Yes, when I activate it, I get "Callout loop not allowed".  Nervous about what'll happen in 100 days when it auto-activates itself.
Steve Berley [Left Propeller]Steve Berley [Left Propeller]
By the way - I'm not using it in batch.  Rather, I'm initiated by a trigger which calls the @future method makes a REST call back to SFDC which ultimately calls getContent().
Murray McDonaldMurray McDonald
Yes -- that would still qualify as a callout loop under the new rules.  So you need to eliminate the REST call in  the @future method and instead do the getContent() call directly in the the @future method.  The good news is that this is now allowed in Winter 16 and the getContent() runs in the context of a user sesion so if the getContent() is hitting a page that requires user athentication it will be fine.
AnupPrakash_AlgoworksAnupPrakash_Algoworks
The Solution is achieved by using webservice. You need to throw the request to an external system and then make a callout to SF and then do the getContent/getContentAsPDF. Worked like a charm for me.
Emily Davis 13Emily Davis 13
Anup, I think what Murray is getting at is that there is no need to throw the request to an eternal system or even make an HTTP callout at all. As of Winter '16, you can simply call getContent()/getContentAsPDF() directly within your future method. This allows you to call these methods directly within trigger or batch code, without using a webservice. This is what our code looks like:
@future (callout=true)
public static void createPDF(Id pid, String name, String description) {		
	Attachment a;
	
    PageReference pdf = Page.GiftReceiptDoc;
    pdf.getParameters().put('pid', pid);
    

    if (!Test.isRunningTest()) {
       a = new Attachment(
            ParentId        = pid,
            name            = name,
            body            = pdf.getContentAsPDF(),
            description     = description,
            ContentType     = 'application/pdf',
            isPrivate       = false
        );
        insert a;

        // Run code to send email with attachment
    }
}

WARNING: We've been having some issues getting this to run successfully as a Force.com Site Guest User. We are still investigating a workaround for this.
Emily Davis 13Emily Davis 13
By the way, does anyone have an update on when this update is scheduled for auto-activation? The latest update that I've found says it's scheduled for Summer '16:
http://docs.releasenotes.salesforce.com/en-us/spring16/release-notes/rn_vf_pagereference_getcontent_cruc.htm
 
SalesforceTechieSalesforceTechie
Hi Abdelhakim,

I used your logic, but no luck.
AbdelhakimAbdelhakim
Hi SalesforceTechie,

This issue is solved is this link http://releasenotes.docs.salesforce.com/en-us/winter16/release-notes/rn_apex_pagereference_getcontent.htm#rn_apex_pagereference_getcontent
 
SfdcTechieSfdcTechie
Hi Sean Parker 11,

Can I have your email Id so that I can send you my class file for your help?
Thank you.
Steve Berley [Left Propeller]Steve Berley [Left Propeller]
Have you made sure to add salesforce.com to your Remote Site Settings?  it's an easy thing to miss and it keeps everything from working...
Dan VegaDan Vega
We were able to adjust our code to generate the PDF directly.  That is, we removed the HTTP callout that was acting as the middle man.  This change worked both before and after the critical update was enabled, so we were able to make this change and deploy it without having to coordinate the citical update activation.  In our case, we are using a scheduled batch job to call the PDF generation code:

PageReference p = Page.MyCustomPage;
Blob appBlob;
if(Test.IsRunningTest()) {
    appBlob = Blob.valueOf('Test PDF');
} else {
    appBlob = p.getContent();
}

and the page has a header like: 
<apex:page controller="MyCustomPageController" applyHtmlTag="false" applyBodyTag="false" renderAs="pdf" showHeader="false" sidebar="false" standardStylesheets="false">

Hope this helps,
Dan

 
Hara Sahoo 14Hara Sahoo 14
Hi Guys,
I am getting the error:System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out

The reason I believe is due to the getcontent() callout within a for loop.
This was working fine till the Summer 15 critical update.
If i run the code for a single list item,it works.It fails when it starts iterating.
Any assistance will be appreciated.
global class Exporter implements System.Schedulable

    global void execute(SchedulableContext sc) //USED FUTURE ANNOTATION//
    {
        CalloutExport();
    }    
    @future(callout=true)
    public static void CalloutExport()
    {
        List<Exporter__c> exportList = new List<Exporter__c>{};
            Datetime dt = DateTime.newInstance(Date.today(), Time.newInstance(0, 0, 0, 0));
        Date d = Date.today();
        Date firstDate = d.toStartOfMonth();
        Date lastDay = firstDate.addDays(Date.daysInMonth(d.year(), d.month())-1);
        system.debug('First Day: ' + firstDate);
        system.debug('Last Day: ' + lastDay);
        exportList = [Select Id, Name, Report_ID__c, Attachment_Name__c,Email_Body__c,Email_Subject__c, Email_Recipients__c, Frequency__c, Weekly_Days__c, Monthly_Day__c,Last_Run_DTM__c from Exporter__c
                      where Last_Run_DTM__c != today ];
        for(Exporter__c e : exportList){
            //Determine if Exporter record is scheduled to run today.
            Boolean process = Test.isRunningTest() ? true : false;
           
            //Test for Weekly frequency.
            process = e.Frequency__c == 'Weekly' && e.Weekly_Days__c.contains(dt.format('EEEE')) ? true : process;
           
            //Test for Monthly frequency.
            process = e.Frequency__c == 'Monthly' && (e.Monthly_Day__c == String.valueOf(d.day()) || e.Monthly_Day__c == 'last' && d == lastDay || lastDay.day() <= Integer.valueOf(e.Monthly_Day__c)) ? true : process;
           
            //Run process if scheduled.
            if(process){
                System.debug('Starting message processing for: ' + e.Name);
               
                ApexPages.PageReference report = new ApexPages.PageReference('/' + e.Report_ID__c + '?csv=1');
                Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
                attachment.setFileName(e.Attachment_Name__c);
                Blob content = report.getContent();//THIS IS WHERE IT GET'S THE EXCEPTION//
                attachment.setBody(content);
                attachment.setContentType('text/csv');
                Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
                message.setFileAttachments(new Messaging.EmailFileAttachment[] {attachment});
                message.setSubject(e.Email_Subject__c);
                message.setPlainTextBody(e.Email_Body__c);
                String[] emailRecipients = e.Email_Recipients__c.split(',');
                message.setToAddresses(emailRecipients);
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] {message});
                System.debug('Completed message processing for: ' + e.Name);
                e.Last_Run_DTM__c=Datetime.now();
                update e;
               
            }
           
        }
    }
}