let’s understand how to handler the apex callouts during the salesforce read-only mode.During read-only mode, Apex callouts to external services execute and aren’t blocked by the system. Typically, you might execute some follow-up operations in the same transaction after receiving a response from a callout. For example, you might make a DML call to update a Salesforce record. But write operations in Salesforce, such as record updates, are blocked during read-only mode. Instance refreshes result in periods of read-only mode to facilitate infrastructure upgrades. Another is site switches. Continuous site switching enables Salesforce to improve our operations and infrastructure and meet the compliance requirement of many of our customers. Planned instance refreshes and site switches will put your Salesforce org in read-only mode for a portion of your preferred maintenance windows. To check whether the org is in read-only mode, call System.getApplicationReadWriteMode(). The following example checks the return value of System.getApplicationReadWriteMode(). If the return value is equal to ApplicationReadWriteMode.READ_ONLY enum value, the org is in read-only mode and the callout is skipped. Otherwise (ApplicationReadWriteMode.DEFAULT value), the callout is performed.
To test read-only mode in the sandbox, contact Salesforce to enable the read-only mode test option. Once the test option is enabled, you can toggle read-only mode on and verify your apps. Here is the code
public class AccountMatchReadOnly {
public class MyReadOnlyException extends Exception {}
public void getCalloutResponseContents(List<Account> acc) {
// Get Read-only mode status
ApplicationReadWriteMode mode = System.getApplicationReadWriteMode();
if (mode == ApplicationReadWriteMode.READ_ONLY) {
// Prevent the callout
throw new MyReadOnlyException('Read-only mode. Skipping callouts!');
} else if (mode == ApplicationReadWriteMode.DEFAULT) {
// Instantiate a new http object
for(Account a :acc){
String addStr = EncodingUtil.urlEncode(a.BillingStreet+','+a.BillingCity+','+a.BillingCountry , 'UTF-8');
Http h = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://api.addressfinder.io/api/nz/address/cleanse?key=68JQFL39HNBKDUGWTVY4&secret=MHXT7LJBWYNGPF98KRV&q='+addStr+'&format=json');
request.setMethod('POST');
request.setHeader('Content-Type','application/json');
HttpResponse response = h.send(request);
if (response.getStatusCode() == 200) {
Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
//System.debug('results'+results);
Boolean isMatched =(Boolean) results.get('matched') ;
if(isMatched){
a.Verification_Status__c = 'Completed' ;
// update a ;
}else{
a.Verification_Status__c = 'Pending' ;
// update a ;
}
}
}
update acc ;
}
}
}
Write a trigger for ChatterMessage to automate the moderation of private messages in an organization or community. Use triggers to ensure that messages conform to your company’s messaging policies and don’t contain blacklisted words.Although you can create an after insert trigger, ChatterMessage is not updatable, and consequently, any after insert trigger that modifies ChatterMessage will fail at runtime with an appropriate error message.Here the trigger
trigger ChatterMessageTrigger on ChatterMessage (before insert) {
ChatterMessage[] messages = Trigger.new;
MessageModerator moderator = MessageModerator.getInstance();
for (ChatterMessage currentMessage : messages) {
moderator.review(currentMessage);
}
}
This example shows a before insert trigger on ChatterMessage that is used to review each new message. This trigger calls a class method, moderator.review(), to review each new message before it is inserted. If a message violates your policy, for example when the message body contains blacklisted words, you can prevent the message from being sent by calling the Apex addError method. You can call addError to add a custom error message on a field or on the entire message. The following snippet shows a portion of the review content method that adds an error to the message Body field.
public class MessageModerator {
private Static List<String> blacklistedWords=null;
private Static MessageModerator instance=null;
public void review(ChatterMessage theMessage) {
reviewContent(theMessage);
reviewSender(theMessage);
}
public void reviewContent(ChatterMessage theMessage) {
String proposedMsg=theMessage.Body.toLowerCase();
boolean problemsFound=false; // Assume it's acceptable
// Iterate through the blacklist looking for matches
for (String nextBlackListedWord : blacklistedWords) {
if (proposedMsg.contains(nextBlackListedWord)) {
theMessage.Body.addError(
'This message does not conform to the acceptable use policy');
System.debug('moderation flagged message with word: '
+ nextBlackListedWord);
problemsFound=true;
break;
}
}
// For demo purposes, we're going to add a "seal of approval" to the
// message body which is visible.
if (!problemsFound) {
theMessage.Body = theMessage.Body +
' *** approved, meets conduct guidelines';
}
}
public void reviewSender(ChatterMessage theMessage) {
// Are we in a Community Context?
boolean isCommunityContext = (theMessage.SendingNetworkId != null);
// Get the User
User sendingUser = [SELECT Id, Name, UserType, IsPortalEnabled
FROM User where Id = :theMessage.SenderId ];
// ...
}
public static MessageModerator getInstance() {
if (instance==null) {
instance = new MessageModerator();
}
return instance;
}
private MessageModerator() {
initializeBlackList();
}
private void initializeBlackList() {
if (blacklistedWords==null) {
blacklistedWords = new List<String> ();
blacklistedWords.add('heavy lifting') ;
blacklistedWords.add('Real-time') ;
blacklistedWords.add('UnHealthy') ;
}
}
}
The below image show when you are trying to send the chatter message with the body that contains blacklisted words then it will through an error message.
In this blog post, we are going to see how to use salesforce apex scheduling capabilities which support invoking the Apex class at specific times like daily or weekly or even every minute. To invoke Apex classes to run at specific times, first implement the Schedulable interface for the class, then specify the schedule using either the Schedule Apex page in the Salesforce user interface or the System.schedule method.
Apex Syntax
To schedule an Apex class to run at regular intervals, first, write an Apex class that implements the Salesforce-provided interface Schedulable.The schedulable interface is having the only execute method.
global void execute(SchedulableContext sc){}
execute method will take the SchedulableContext object as an argument which will help to track the scheduled job once it’s scheduled. The SchedulableContext getTriggerID method returns the ID of the CronTrigger object associated with this scheduled job as a string. You can query CronTriggerto track the progress of the scheduled job.To stop the execution of a job that was scheduled, use the System.abortJobmethod with the ID returned by the getTriggerIDmethod.Here is the sample code
global class scheduledApex implements Schedulable {
global void execute(SchedulableContext SC) {
DateTime dt = System.now();
Long timeinNumber = dt.getTime();
Account a = new Account() ;
a.Name ='Account @'+String.valueOf(timeinNumber);
insert a;
}
}
Scheduling Apex
You can schedule the salesforce apex by using System.Schedule method or by using Salesforce UI. The main difference is Salesforce UI won’t support the Schedule job in Seconds and minutes which can be done with System.Schedule.The System.Schedule method uses the user’s timezone on the basis of all schedules, but runs in system mode—all classes are executed, whether or not the user has permission to execute the class.The System.Schedule method takes three arguments: a name for the job, an expression used to represent the time and date the job is scheduled to run, and the name of the class. This expression has the following syntax:
You can also schedule a class using the user interface.
Go to Apex Classes.
Click Schedule Apex
Click Save.
Monitoring
After the Apex job has been scheduled you can track the schedule details from the Salesforce UI or by running a SOQL query on CronTrigger as shown below.
CronTrigger ct =
[SELECT TimesTriggered, NextFireTime
FROM CronTrigger WHERE Id = :jobID];
The jobID variable holding the ID of the job. The System.schedule method returns the job ID. If you’re performing this query inside the execute method of your schedulable class, you can obtain the ID of the current job by calling getTriggerId on the SchedulableContext argument variable. Assuming this variable name is sc, the modified example becomes:
CronTrigger job =
[SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType
FROM CronTrigger ORDER BY CreatedDate DESC LIMIT 1];
You can also get the job’s name and the job’s type from the CronJobDetail record associated with the CronTrigger record. To do so, use the CronJobDetail relationship when performing a query on CronTrigger. This example retrieves the most recent CronTrigger record with the job name and type from CronJobDetail.
CronJobDetail ctd =
[SELECT Id, Name, JobType
FROM CronJobDetail WHERE Id = :job.CronJobDetail.Id];
Test Class
@isTest
private class scheduledApexTest {
static testmethod void test() {
Test.startTest();
Account a = new Account();
a.Name = 'testscheduledApex';
insert a;
String jobId = System.schedule('testscheduledApex',
'0 0 0 3 9 ? 2022' ,
new scheduledApex());
// Get the information from the CronTrigger API object
CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered,
NextFireTime
FROM CronTrigger WHERE id = :jobId];
// Verify the expressions are the same
System.assertEquals('0 0 0 3 9 ? 2022',ct.CronExpression);
// Verify the job has not run
System.assertEquals(0, ct.TimesTriggered);
Test.stopTest();
}
}
Things to Remember
Salesforce schedules the class for execution at the specified time. Actual execution may be delayed based on service availability.
You can only have 100 scheduled Apex jobs at one time
Use extreme care if you’re planning to schedule a class from a trigger. You must be able to guarantee that the trigger won’t add more scheduled classes than the limit.
If there are one or more active scheduled jobs for an Apex class, you cannot update the class or any classes referenced by this class through the Salesforce user interface. However, you can enable deployments to update the class with active scheduled jobs by using the Metadata API (for example, when using the Force.com IDE
Synchronous Web service callouts are not supported from scheduled Apex.
The maximum number of scheduled Apex executions per a 24-hour period is 250,000 or the number of user licenses in your organization multiplied by 200, whichever is greater. This limit is for your entire org and is shared with all asynchronous Apex: Batch Apex, Queueable Apex, scheduled Apex, and future methods.
Apex jobs scheduled to run during a Salesforce service maintenance downtime will be scheduled to run after the service comes back up, when system resources become available. If a scheduled Apex job was running when downtime occurred, the job is rolled back and scheduled again after the service comes back up.
In this blog, I am going to explain how to use salesforce Continuations Asynchronous Callouts from the visualforce page.Continuations Apex feature that allows you to escape the limit of ten concurrent long-running callouts. Asynchronous Callouts work through a pattern called a Continuation; your controller creates its HTTP request as it normally would, but, instead of firing it off there and then, you create a Continuation object, pass the HTTP request and an Apex callback method to the Continuation, and return it to the platform for processing.Use asynchronous callouts to make long-running requests from a Visualforce page to an external Web service and process responses in callback methods. Asynchronous callouts that are made from a Visualforce page don’t count toward the Apex limit of 10 synchronous requests that last longer than five seconds. As a result, you can make more long-running callouts and you can integrate your Visualforce pages with complex back-end assets.An asynchronous callout is a callout that is made from a Visualforce page for which the response is returned through a callback method. An asynchronous callout is also referred to as a continuation.This diagram shows the execution path of an asynchronous callout, starting from a Visualforce page. A user invokes an action on a Visualforce page that requests information from a Web service (step 1). The app server hands the callout request to the Continuation server before returning to the Visualforce page (steps 2–3). The Continuation server sends the request to the Web service and receives the response (steps 4–7), then hands the response back to the app server (step 8). Finally, the response is returned to the Visualforce page (step 9).
Using Continuation
Step1: Action Method
To use asynchronous callouts, create a Continuation object in an action method of a controller, and implement a callback method.To invoke an asynchronous callout, call the external service by using a Continuation instance in your Visualforce action method. When you create a continuation, you can specify a timeout value and the name of the callback method. For example, the following creates a continuation with a 60-second timeout and a callback method name of processResponse.
Continuation cont = new Continuation(60);
cont.continuationMethod = 'processResponse';
Next, associate the Continuation object to an external callout.
The method that invokes the callout (the action method) must return the Continuation object to instruct Visualforce to suspend the current request after the system sends the callout and waits for the callout response. The Continuation object holds the details of the callout to be executed.Here is the complete code to invoke from the action.
public Object startRequest() {
// Create continuation with a timeout
Continuation con = new Continuation(40);
// Set callback method
con.continuationMethod='processResponse';
// Create callout request
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(LONG_RUNNING_SERVICE_URL);
// Add callout request to continuation
this.requestLabel = con.addHttpRequest(req);
// Return the continuation
return con;
}
Step 2: Defining a Callback Method
The response is returned after the external service finishes processing the callout. You can specify a callback method for asynchronous execution after the callout returns. This callback method must be defined in the controller class where the callout invocation method is defined. You can define a callback method to process the returned response, such as retrieving the response for display on a Visualforce page as shown below .
// Callback method
public Object processResponse() {
// Get the response by using the unique label
HttpResponse response = Continuation.getResponse(this.requestLabel);
// Set the result variable that is displayed on the Visualforce page
//this.result = response.getBody();
this.jsonParserResult = (List<JSONAlbums>) System.JSON.deserialize(response.getBody(), List<JSONAlbums>.class);
// Return null to re-render the original Visualforce page
return null;
}
Example code
In the following example application, the button action is implemented in an Apex controller method. The action method creates a Continuation and returns it. After the request is sent to the service, the Visualforce request is suspended. The user must wait for the response to be returned before proceeding with using the page and invoking new actions. When the external service returns a response, the Visualforce request resumes and the page receives this response.
Simple Apex class
public class ContinuationRestCall {
// Unique label corresponding to the continuation
public String requestLabel;
// Result of callout
public String result {get;set;}
// Callout endpoint as a named credential URL
// or, as shown here, as the long-running service URL
private static final String LONG_RUNNING_SERVICE_URL = 'https://jsonplaceholder.typicode.com/users/1/albums';
public List<JSONAlbums> jsonParserResult {get;set;}
// Action method
public Object startRequest() {
// Create continuation with a timeout
Continuation con = new Continuation(40);
// Set callback method
con.continuationMethod='processResponse';
// Create callout request
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(LONG_RUNNING_SERVICE_URL);
// Add callout request to continuation
this.requestLabel = con.addHttpRequest(req);
// Return the continuation
return con;
}
// Callback method
public Object processResponse() {
// Get the response by using the unique label
HttpResponse response = Continuation.getResponse(this.requestLabel);
// Set the result variable that is displayed on the Visualforce page
//this.result = response.getBody();
this.jsonParserResult = (List<JSONAlbums>) System.JSON.deserialize(response.getBody(), List<JSONAlbums>.class);
// Return null to re-render the original Visualforce page
return null;
}
public class JSONAlbums {
public Integer userId{get;set;}
public Integer id{get;set;}
public String title{get;set;}
}
}
Visuaforce page
<apex:page controller="ContinuationRestCall" showChat="false" showHeader="false" >
<apex:form >
<!-- Invokes the action method when the user clicks this button. -->
<apex:commandButton action="{!startRequest}"
value="Start Request" reRender="result" />
</apex:form>
<apex:pageBlock id="result" title="Process for Using Asynchronous Callouts">
<apex:pageBlockSection columns="1">
<apex:pageBlockTable value="{!jsonParserResult}" var="res" >
<apex:column value="{!res.userId}" headerValue="userId"/>
<apex:column value="{!res.id}" headerValue="id"/>
<apex:column value="{!res.title}" headerValue="title"/>
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:page>
@isTest
private class ContinuationRestCall_Test {
public static testmethod void testWebService() {
ContinuationRestCall controller = new ContinuationRestCall();
// Invoke the continuation by calling the action method
Continuation conti = (Continuation)controller.startRequest();
// Verify that the continuation has the proper requests
Map<String, HttpRequest> requests = conti.getRequests();
system.assert(requests.size() == 1);
system.assert(requests.get(controller.requestLabel) != null);
// Perform mock callout
// (i.e. skip the callout and call the callback method)
HttpResponse response = new HttpResponse();
response.setBody('Mock response body');
// Set the fake response for the continuation
Test.setContinuationResponse(controller.requestLabel, response);
// Invoke callback method
Object result = Test.invokeContinuationMethod(controller, conti);
// result is the return value of the callback
System.assertEquals(null, result);
// Verify that the controller's result variable
// is set to the mock response.
System.assertEquals('Mock response body', controller.result);
}
}
Continuation Status Code
2000: The timeout was reached, and the server didn’t get a chance to respond.
2001: There was a connection failure.
2002: Exceptions occurred.
2003: The response hasn’t arrived (which also means that the Apex asynchronous callout framework hasn’t resumed).
2004: The response size is too large (greater than 1 MB).
Limitation
You can make up to three asynchronous callouts in a single continuation.
Asynchronous callouts are supported only through a Visualforce page. Making an asynchronous callout by invoking the action method outside a Visualforce page, such as in the Developer Console, isn’t supported.
Asynchronous callouts are available for Apex controllers and Visualforce pages saved in version 30.0 and later. If JavaScript remoting is used, version 31.0 or later is required.
This Continuation pattern does not allow you to make DML operations before the web service is invoked.
The server has to return within 2 minutes, or there will be a timeout
To make multiple callouts to a long-running service simultaneously from a Visualforce page, you can add up to three requests to the Continuation instance. An example of when to make simultaneous callouts is when you’re making independent requests to a service, such as getting inventory statistics for two products.When you’re making multiple callouts in the same continuation, the callout requests run in parallel and suspend the Visualforce request. Only after all callout responses are returned does the Visualforce process resume.The following Visualforce and Apex examples show how to make two asynchronous callouts simultaneously by using a single continuation.
public class MultipleContinutationController {
// Unique label for the first request
public String requestLabel1;
// Unique label for the second request
public String requestLabel2;
// Result of first callout
public String result1 {get;set;}
// Result of second callout
public String result2 {get;set;}
// Endpoints of long-running service
private static final String LONG_RUNNING_SERVICE_URL1 = 'http://inqstatsapi.inqubu.com?api_key=27b3c066fcedd91a&data=population&countries=us';
private static final String LONG_RUNNING_SERVICE_URL2 = 'http://inqstatsapi.inqubu.com?api_key=27b3c066fcedd91a&data=population&countries=gb';
public List<JSON2Apex> jsonApexURL1 {get;set;}
public List<JSON2Apex> jsonApexURL2 {get;set;}
// Action method
public Object startRequestsInParallel() {
// Create continuation with a timeout
Continuation con = new Continuation(60);
// Set callback method
con.continuationMethod='processAllResponses';
// Create first callout request
HttpRequest req1 = new HttpRequest();
req1.setMethod('GET');
req1.setEndpoint(LONG_RUNNING_SERVICE_URL1);
// Add first callout request to continuation
this.requestLabel1 = con.addHttpRequest(req1);
// Create second callout request
HttpRequest req2 = new HttpRequest();
req2.setMethod('GET');
req2.setEndpoint(LONG_RUNNING_SERVICE_URL2);
// Add second callout request to continuation
this.requestLabel2 = con.addHttpRequest(req2);
// Return the continuation
return con;
}
// Callback method.
// Invoked only when responses of all callouts are returned.
public Object processAllResponses() {
// Get the response of the first request
HttpResponse response1 = Continuation.getResponse(this.requestLabel1);
this.result1 = response1.getBody();
jsonApexURL1 = (List<JSON2Apex>) System.JSON.deserialize(response1.getBody(), List<JSON2Apex>.class);
// Get the response of the second request
HttpResponse response2 = Continuation.getResponse(this.requestLabel2);
this.result2 = response2.getBody();
jsonApexURL2 = (List<JSON2Apex>) System.JSON.deserialize(response2.getBody(), List<JSON2Apex>.class);
// Return null to re-render the original Visualforce page
return null;
}
public class JSON2Apex {
public String countryCode{get;set;}
public String countryName{get;set;}
public List<Population> population{get;set;}
}
public class Population {
public String year{get;set;}
public String data{get;set;}
}
public static List<JSON2Apex> parse(String json) {
return (List<JSON2Apex>) System.JSON.deserialize(json, List<JSON2Apex>.class);
}
}
<apex:page controller="MultipleContinutationController" showChat="false" showHeader="false">
<apex:form >
<!-- Invokes the action method when the user clicks this button. -->
<apex:commandButton action="{!startRequestsInParallel}" value="Start Request" reRender="panel"/>
</apex:form>
<apex:outputPanel id="panel">
<apex:pageBlock id="result" >
<apex:pageBlockSection >
Result 1 - {!jsonApexURL1}
<br/>
<br/>
<br/>
Result 2 - {!jsonApexURL2}
</apex:pageBlockSection>
</apex:pageBlock>
</apex:outputPanel>
</apex:page>