tag:blogger.com,1999:blog-14595111786266342512024-03-13T03:03:03.014-07:00Chintan Shah's BlogChintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.comBlogger153125tag:blogger.com,1999:blog-1459511178626634251.post-73326550130306649512020-01-16T13:40:00.004-08:002020-07-20T08:08:53.038-07:00Chrome update - SameSite Lax (Salesforce impact)<iframe allowfullscreen="true" frameborder="0" height="550" scrolling="no" src="https://screencast-o-matic.com/embed?sc=cYVbqmvHkd&v=5&title=0&ff=1" width="480"></iframe>
<br />
<br />
Chrome February 2020 update can break many integration which relies on cookies (which is heavily used in iframe based integration). Salesforce internally uses iframe to render VF pages on lightning, so that is broken as well as of now, till salesforce fixes it.<br />
<br />
<b>How does cookies work</b> (especially in cross site or iframe - when multiple sites are involved)<br />
<br />
E.g. let's say salesforce.com is hosting site, and inside the salesforce.com we are hosting facebook.com as an iframe.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/facebook1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="800" height="360" src="https://springsoa.com/blogs/SameSite/facebook1.png" width="640" /></a></div>
<br />
<br />
1) user logs in to facebook.com, and upon login facebook saves the cookies about user session<br />
2) user logs in to salesforce.com where we are hosting facebook.com iframe. When browser loads facebook in iframe, it passes the facebook cookies to facebook, so it is not challenged with username and password<br />
<br />
This is good thing, however in certain cases if salesforce.com has bad code and/or facebook.com has vulnerable code, then facebook.com is vulnerable to cross side scripting attack.<br />
<br />
With chrome February update, all cookies will be treated with SameSite=Lax if SameSite is not specified. Which means, in this case, browser will not send the cookies to facebook.com if it is coming from salesforce.com.<br />
If you open facebook.com on separate browser tab, it would pass the cookies but not if embedded in any other site.<br />
<br />
<b>Solution</b><br />
Other site will need to store the cookies with SameSite=None in order to get the old behavior.<br />
<br />
<br />
For demo purpose, here is the Heroku web server, which stores cookies in different fashion and displays the cookies<br />
<ul>
<li>displayCookies.html : displays cookies belongs to this site </li>
<li>storeCookies.html - stores cookies with no SameSite information (meaning it would be treated with None before February, and Lax after February release)</li>
<li>storeCookiesNone.html - stores cookies with SameSite=None</li>
<li>storeCookiesLax.html - stores cookies with SameSite=Lax </li>
<li>storeCookiesStrict.html - stores cookies with SameSite=Strict</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/SaveCookieHeroku.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="800" height="302" src="https://springsoa.com/blogs/SameSite/SaveCookieHeroku.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
On Salesforce I have visualforce page, which points to displayCookies.html<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/VF_displayCookies.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="800" height="158" src="https://springsoa.com/blogs/SameSite/VF_displayCookies.png" width="640" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
<b><br /></b>
<br />
<h3>
<b>Default Chrome Behavior (before February 2020)</b></h3>
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/defaultBehavior_beforeFeb2020.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="269" data-original-width="800" height="214" src="https://springsoa.com/blogs/SameSite/defaultBehavior_beforeFeb2020.png" width="640" /></a></div>
<b><br /></b></div>
<div>
<ul>
<li><b>Scenario 1</b> : on browser go to storeCookies.html, then go to salesforce visual force page. This is working as expected</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/storeCookies_html.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="303" data-original-width="800" src="https://springsoa.com/blogs/SameSite/storeCookies_html.png" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/preFebStoreCookie.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="279" data-original-width="512" height="217" src="https://springsoa.com/blogs/SameSite/preFebStoreCookie.png" width="400" /></a></div>
<br />
<ul>
<li><b>Scenario 2</b> : on browser goto storeCookiesNone.html, then go to salesforce visual force page. This is also working as expected. as it is default behavior.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/storeCookiesNone_html.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="800" height="164" src="https://springsoa.com/blogs/SameSite/storeCookiesNone_html.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/preFebStoreCookieNone.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="124" data-original-width="533" height="92" src="https://springsoa.com/blogs/SameSite/preFebStoreCookieNone.png" width="400" /></a></div>
<br />
<br />
<ul>
<li><b>Scenario 3</b> : on browser go to storeCookiesLax.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/storeCookiesLax_html.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="208" data-original-width="800" height="166" src="https://springsoa.com/blogs/SameSite/storeCookiesLax_html.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/preFebStoreCookieLax.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="109" data-original-width="607" height="71" src="https://springsoa.com/blogs/SameSite/preFebStoreCookieLax.png" width="400" /></a></div>
<br />
<br />
<ul>
<li><b>Scenario 4</b> : on browser goto storeCookiesStrict.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior.</li>
</ul>
</div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/storeCookiesStrict_html.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="221" data-original-width="800" height="176" src="https://springsoa.com/blogs/SameSite/storeCookiesStrict_html.png" width="640" /></a></div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/preFebStoreCookieLax.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="109" data-original-width="607" height="71" src="https://springsoa.com/blogs/SameSite/preFebStoreCookieLax.png" width="400" /></a></div>
<br /></div>
<div>
<b><br /></b>
<b><br /></b>
<br />
<h3>
<b>Default Chrome Behavior (after February 2020)</b></h3>
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/defaultBehavior_afterFeb2020.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="311" data-original-width="800" height="248" src="https://springsoa.com/blogs/SameSite/defaultBehavior_afterFeb2020.png" width="640" /></a></div>
<b><br /></b></div>
<div>
<div>
<ul>
<li>Scenario 1 : on browser goto storeCookies.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior. This would be working before February update.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/postFebStoreCookie.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="149" data-original-width="609" height="97" src="https://springsoa.com/blogs/SameSite/postFebStoreCookie.png" width="400" /></a></div>
<br />
<ul>
<li>Scenario 2 : on browser goto storeCookiesNone.html, then go to salesforce visual force page. <b>This is working as expected</b>!</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/SameSite/postFebStoreCookieNone.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="165" data-original-width="498" height="132" src="https://springsoa.com/blogs/SameSite/postFebStoreCookieNone.png" width="400" /></a></div>
<br />
Server side code for this:<br />
<br />
<pre style="background-color: white; font-family: consolas; font-size: 9.8pt;">response.header(<span style="color: green; font-weight: bold;">"Set-Cookie"</span>,<span style="color: green; font-weight: bold;">"Heroku-Username=Anonymous-StoreCookie-None; Secure; SameSite=None"</span>);
response.header(<span style="color: green; font-weight: bold;">"Set-Cookie"</span>,<span style="color: green; font-weight: bold;">"Heroku-SessionId=SessionId-StoreCookie-None; Secure; SameSite=None"</span>);</pre>
<br />
<ul>
<li>Scenario 3 : on browser goto storeCookiesLax.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior.</li>
</ul>
similar to Scenario 1<br />
<ul>
<li>Scenario 4 : on browser goto storeCookiesStrict.html, then go to salesforce visual force page.This is removing cookies from iframe, which could cause undesired behavior.</li>
</ul>
</div>
<div>
similar to Scenario 1</div>
</div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com4tag:blogger.com,1999:blog-1459511178626634251.post-40549034610238805012019-12-28T15:31:00.002-08:002020-01-15T18:38:08.226-08:00Loopback calls - Local calls in salesforce<br/>
<iframe allowfullscreen="true" frameborder="0" height="652" scrolling="no" src="https://screencast-o-matic.com/embed?sc=cqlUn0vYy5&v=5&title=0&ff=1" width="980"></iframe>
<br/>
<br/>
There are many situations when we have to make back to salesforce - e.g.<br />
<br />
a) if we want to use tooling api or rest api for certain reason<br />
b) if managed package is licensed to only certain user, and we want to proxy user<br />
c) we want to perform action as some another user<br />
<br />
Below solution is not highly advisable but it is hack that gets job done:<br />
<br />
<b>1) Named Credentials</b><br />
Create named credential for the proxy user, this is the user which will be used to call web service<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/localEntry/NamedCredentials.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="800" data-original-width="715" height="640" src="https://springsoa.com/blogs/localEntry/NamedCredentials.png" width="572" /></a></div>
<br />
<br />
<br />
<b>2) Generic HTTP Call out</b><br />
Generic http callout method for calling out so we can reuse it multiple times<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18</pre>
</td><td><pre style="line-height: 125%; margin: 0;">public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">HttpService</span> {
public static String callout(String endPoint, String httpMethod, Map<span style="color: #333333;"><</span>String, String<span style="color: #333333;">></span> headers, String body, Integer timeout) {
Http http <span style="color: #333333;">=</span> new Http();
HttpRequest httpRequest <span style="color: #333333;">=</span> new HttpRequest();
httpRequest<span style="color: #333333;">.</span>setTimeout(timeout);
httpRequest<span style="color: #333333;">.</span>setEndpoint(endPoint);
httpRequest<span style="color: #333333;">.</span>setMethod(httpMethod);
<span style="color: #008800; font-weight: bold;">for</span>(String key : headers<span style="color: #333333;">.</span>keySet() ) {
httpRequest<span style="color: #333333;">.</span>setHeader(key, headers<span style="color: #333333;">.</span>get(key) );
}
httpRequest<span style="color: #333333;">.</span>setBody(body);
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'HttpService.callout request : '</span> <span style="color: #333333;">+</span> httpRequest);
HttpResponse httpResponse <span style="color: #333333;">=</span> http<span style="color: #333333;">.</span>send(httpRequest);
String responseBody <span style="color: #333333;">=</span> httpResponse<span style="color: #333333;">.</span>getBody();
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'HttpService.callout responseBody : '</span> <span style="color: #333333;">+</span> responseBody);
<span style="color: #008800; font-weight: bold;">return</span> responseBody;
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<br />
<b>3) A demo web service </b><br />
<b><br /></b>
This is demo rest service which just prints the current user in session - inside we can put any logic that needs to be executed as proxy user<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">12</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">27</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
<span style="color: #555555; font-weight: bold;">@RestResource</span>(urlMapping<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">'/DemoRestService/*'</span>)
<span style="color: #008800; font-weight: bold;">global</span> <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">DemoRestService</span> {
<span style="color: #555555; font-weight: bold;">@HttpGet</span>
<span style="color: #008800; font-weight: bold;">global</span> static String doGet() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'DemoRestService.doGet currentUser '</span> <span style="color: #333333;">+</span> UserInfo<span style="color: #333333;">.</span>getUserName() );
<span style="color: #008800; font-weight: bold;">return</span> [ select <span style="color: #007020;">id</span> <span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">Account</span> <span style="color: #0e84b5; font-weight: bold;">limit</span> <span style="color: #0000dd; font-weight: bold;">1</span>]<span style="color: #333333;">.</span>Name;
}
<span style="color: #555555; font-weight: bold;">@HttpPost</span>
<span style="color: #008800; font-weight: bold;">global</span> static String doPost(String name) {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'DemoRestService.doPost currentUser '</span> <span style="color: #333333;">+</span> UserInfo<span style="color: #333333;">.</span>getUserName() <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' name '</span> <span style="color: #333333;">+</span> name );
<span style="color: #008800; font-weight: bold;">return</span> name;
}
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
We will also need to setup security so that proxy user's profile has access to the Apex class<br />
<br />
<br />
<b>4) Get proxy user's session id</b><br />
A service to get session id token using named credential, which can be used to call any rest service or soap service internally using the proxy user<br />
<br />
To get token using rest service, you need to create connected app, but with SOAP that is not needed. Hence, I just use soap service to login as proxy user via named credentials and get the session id back. A little XML parsing doesn't hurt much either.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36</pre>
</td><td><pre style="line-height: 125%; margin: 0;"> <span style="color: #555555; font-weight: bold;">@TestVisible</span>
private static String getValueFromXMLString(string xmlString, string keyField){
string valueFound <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">''</span>;
<span style="color: #008800; font-weight: bold;">if</span>( xmlString<span style="color: #333333;">.</span>contains(<span style="background-color: #fff0f0;">'<'</span> <span style="color: #333333;">+</span> keyField <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'>'</span>) <span style="color: #333333;">&&</span> xmlString<span style="color: #333333;">.</span>contains(<span style="background-color: #fff0f0;">'</'</span> <span style="color: #333333;">+</span> keyField <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'>'</span>) ) {
<span style="color: #008800; font-weight: bold;">try</span>{
valueFound <span style="color: #333333;">=</span> xmlString<span style="color: #333333;">.</span>substring(xmlString<span style="color: #333333;">.</span>indexOf(<span style="background-color: #fff0f0;">'<'</span> <span style="color: #333333;">+</span> keyField <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'>'</span>) <span style="color: #333333;">+</span> keyField<span style="color: #333333;">.</span>length() <span style="color: #333333;">+</span> <span style="color: #0000dd; font-weight: bold;">2</span>, xmlString<span style="color: #333333;">.</span>indexOf(<span style="background-color: #fff0f0;">'</'</span> <span style="color: #333333;">+</span> keyField <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'>'</span>));
} catch (exception e){
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'Error in getValueFromXMLString. Details: '</span> <span style="color: #333333;">+</span> e<span style="color: #333333;">.</span>getMessage() <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' keyfield: '</span> <span style="color: #333333;">+</span> keyfield);
}
}
<span style="color: #008800; font-weight: bold;">return</span> valueFound;
}
public static String getTokenNamedCredentials(String namedCredential, String version) {
String body <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com">'</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' <soapenv:Body> '</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' <urn:login> '</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' <urn:username>{!$Credential.Username}</urn:username> '</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' <urn:password>{!$Credential.Password}</urn:password> '</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' </urn:login> '</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' </soapenv:Body> '</span>
<span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' </soapenv:Envelope>'</span>;
String endPoint <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'callout:'</span> <span style="color: #333333;">+</span> namedCredential <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'/services/Soap/u/'</span> <span style="color: #333333;">+</span> version ;
Map<span style="color: #333333;"><</span>String, String<span style="color: #333333;">></span> headers <span style="color: #333333;">=</span> new Map<span style="color: #333333;"><</span>String, String<span style="color: #333333;">></span> {
<span style="background-color: #fff0f0;">'SFDC_STACK_DEPTH'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'1'</span>,
<span style="background-color: #fff0f0;">'SOAPAction'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'DoesNotMatter'</span>,
<span style="background-color: #fff0f0;">'Accept'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'text/xml'</span>,
<span style="background-color: #fff0f0;">'Content-type'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'text/xml'</span>,
<span style="background-color: #fff0f0;">'charset'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'UTF-8'</span>
};
String responseBody <span style="color: #333333;">=</span> callout(endPoint, <span style="background-color: #fff0f0;">'POST'</span>, headers , body, <span style="color: #0000dd; font-weight: bold;">60000</span>);
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'HttpService.getTokenNamedCredentials responseBody : '</span> <span style="color: #333333;">+</span> responseBody );
String token <span style="color: #333333;">=</span> getValueFromXMLString( responseBody, <span style="background-color: #fff0f0;">'sessionId'</span>);
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'HttpService.getTokenNamedCredentials token : '</span> <span style="color: #333333;">+</span> token );
<span style="color: #008800; font-weight: bold;">return</span> token;
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<b><br /></b>
<b>5) Call Demo service </b><br />
Actually calling the demo service using the proxy user's session id. The code is quite simple here.<br />
<br />
Line 8 gets session id for proxy user<br />
Line 9 constructs the rest service url<br />
Line 10 generates the payload<br />
We do have to set the Authorization token in line 14 for header, and then callout is quite simple on line 18.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">12</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">27</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">DemoRestServiceProxyClient</span> {
public static String callDemoServiceOverHttp() {
String token <span style="color: #333333;">=</span> HttpService<span style="color: #333333;">.</span>getTokenNamedCredentials(<span style="background-color: #fff0f0;">'LocalEntry'</span>,<span style="background-color: #fff0f0;">'47.0'</span>);
String endPoint <span style="color: #333333;">=</span> Url<span style="color: #333333;">.</span>getSalesforceBaseUrl()<span style="color: #333333;">.</span>toExternalForm() <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'/services/apexrest/DemoRestService'</span>;
String body <span style="color: #333333;">=</span> JSON<span style="color: #333333;">.</span>serialize( new Map<span style="color: #333333;"><</span>String, Object<span style="color: #333333;">></span> {
<span style="background-color: #fff0f0;">'name'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'Chintan'</span>
} );
Map<span style="color: #333333;"><</span>String, String<span style="color: #333333;">></span> headers <span style="color: #333333;">=</span> new Map<span style="color: #333333;"><</span>String, String<span style="color: #333333;">></span> {
<span style="background-color: #fff0f0;">'Authorization'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'OAuth '</span> <span style="color: #333333;">+</span> token,
<span style="background-color: #fff0f0;">'Content-Type'</span> <span style="color: #333333;">=></span> <span style="background-color: #fff0f0;">'application/json'</span>
};
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' DemoRestServiceProxyClient.callDemoServiceOverHttp endPoint '</span> <span style="color: #333333;">+</span> endPoint <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' token '</span> <span style="color: #333333;">+</span> token <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' body '</span> <span style="color: #333333;">+</span> body );
String response <span style="color: #333333;">=</span> HttpService<span style="color: #333333;">.</span>callout(endPoint, <span style="background-color: #fff0f0;">'POST'</span>, headers, body, <span style="color: #0000dd; font-weight: bold;">60000</span>);
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' DemoRestServiceProxyClient.callDemoServiceOverHttp response '</span> <span style="color: #333333;">+</span> response );
<span style="color: #008800; font-weight: bold;">return</span> response;
}
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<br />
We can see the output. Salesforce doesn't print the session Id, but we can see the output of the call.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/localEntry/DemoClientOutput.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="170" data-original-width="800" height="136" src="https://springsoa.com/blogs/localEntry/DemoClientOutput.png" width="640" /></a></div>
<br />
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-55060550746858338032019-11-11T12:11:00.001-08:002019-11-11T12:12:14.407-08:00Generic Clone Record ComponentIn Salesforce classic, we can use URL hack to clone the record and pre populate the data. In lightning, URL hack doesn't work. However, we can use <span style="background-color: white; color: green; font-family: "consolas"; font-size: 9.8pt; font-weight: bold;"><a href="https://developer.salesforce.com/docs/component-library/bundle/force:createRecord/documentation">e.force:createRecord</a> </span>component to initialize the values for create record.<br />
<br />
For cloning, we need to see previous records and sometime it is custom logic that we need to implement to initialize the value for cloning. Hence, we wrote generic component to take care of this. Currently it only supports fieldset.<br />
<br />
We started with Base Clone Component (CloneBaseComponent)<br />
<br />
<b>CloneBaseComponent.cmp</b><br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 10em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3
4
5
6</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: navy; font-weight: bold;"><aura:component</span> <span style="color: red;">extensible=</span><span style="color: blue;">"true"</span> <span style="color: red;">description=</span><span style="color: blue;">"CloneComponentBase"</span> <span style="color: red;">controller=</span><span style="color: blue;">"CloneComponentBaseController"</span> <span style="color: red;">implements=</span><span style="color: blue;">"force:lightningQuickActionWithoutHeader,lightning:actionOverride,force:hasRecordId"</span><span style="color: navy; font-weight: bold;">></span>
<span style="color: navy; font-weight: bold;"><aura:attribute</span> <span style="color: red;">name=</span><span style="color: blue;">"recordId"</span> <span style="color: red;">type=</span><span style="color: blue;">"String"</span><span style="color: navy; font-weight: bold;">/></span>
<span style="color: navy; font-weight: bold;"><aura:attribute</span> <span style="color: red;">name=</span><span style="color: blue;">"sObjectApiName"</span> <span style="color: red;">type=</span><span style="color: blue;">"String"</span><span style="color: navy; font-weight: bold;">/></span>
<span style="color: navy; font-weight: bold;"><aura:attribute</span> <span style="color: red;">name=</span><span style="color: blue;">"fieldSetName"</span> <span style="color: red;">type=</span><span style="color: blue;">"String"</span><span style="color: navy; font-weight: bold;">/></span>
<span style="color: navy; font-weight: bold;"><aura:handler</span> <span style="color: red;">name=</span><span style="color: blue;">"init"</span> <span style="color: red;">value=</span><span style="color: blue;">"{!this}"</span> <span style="color: red;">action=</span><span style="color: blue;">"{!c.doInit}"</span><span style="color: navy; font-weight: bold;">/></span>
<span style="color: navy; font-weight: bold;"></aura:component></span>
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<b>CloneBaseComponentController.js</b><br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 20em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36</pre>
</td><td><pre style="line-height: 125%; margin: 0;">/**
* Created by Spring7 on 9/19/2019.
*/
({
doInit: function(component, event, helper) {
var action = component.get('c.getRecord');
var recordId = component.get('v.recordId');
var fieldSetName = component.get('v.fieldSetName');
var sObjectApiName = component.get('v.sObjectApiName');
action.setParams({recordId: recordId,fieldSetName:fieldSetName,sObjectApiName:sObjectApiName});
action.setCallback(this, function (response) {
if(response.getState() === 'SUCCESS'){
var defaultFieldValues = response.getReturnValue();
helper.createRecord(component, defaultFieldValues);
helper.closeQuickActionModal(component);
}else if (response.getState() ==="ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] <span style="background-color: #e3d2d2; color: #a61717;">&&</span> errors[0].message) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
"title": "Error",
"message": errors[0].message
});
toastEvent.fire();
helper.closeQuickActionModal(component);
}
} else {
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
}
});
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<b>CloneBaseComponentHelper.js</b><br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 20em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20</pre>
</td><td><pre style="line-height: 125%; margin: 0;">/**
* Created by Spring7 on 9/19/2019.
*/
({
closeQuickActionModal : function(component){
var dismissActionPanel = $A.get('e.force:closeQuickAction');
dismissActionPanel.fire();
},
createRecord : function(component, defaultFieldValues) {
delete defaultFieldValues['Id'];
var createRecordEvent = $A.get('e.force:createRecord');
var sObjectApiName = component.get("v.sObjectApiName");
createRecordEvent.setParams({
entityApiName: sObjectApiName,
defaultFieldValues: defaultFieldValues,
});
createRecordEvent.fire();
}
})
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<b>How to use it</b><br />
<br />
In Salesforce, we can not pass parameter to component when it is bound to a button. Hence we created a new component with just a few lines, which extends the base component and tie to a button.<br />
<br />
<b>CloneAccount </b>Component<br />
It calls CloneBaseComponent with fieldset (CloneAccount) which will be used for copy<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 10em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3
4
5
6
7
8</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-style: italic;"><!--</span>
<span style="color: #008800; font-style: italic;"> - Created by Spring7 on 9/19/2019.</span>
<span style="color: #008800; font-style: italic;"> --></span>
<span style="color: navy; font-weight: bold;"><aura:component</span> <span style="color: red;">description=</span><span style="color: blue;">"CloneAccount"</span> <span style="color: red;">extends=</span><span style="color: blue;">"c:CloneComponentBase"</span> <span style="color: red;">implements=</span><span style="color: blue;">"force:lightningQuickActionWithoutHeader,lightning:actionOverride,force:hasRecordId"</span><span style="color: navy; font-weight: bold;">></span>
<span style="color: navy; font-weight: bold;"><aura:set</span> <span style="color: red;">attribute=</span><span style="color: blue;">"sObjectApiName"</span> <span style="color: red;">value=</span><span style="color: blue;">"Account"</span><span style="color: navy; font-weight: bold;">></span>
<span style="color: navy; font-weight: bold;"><aura:set</span> <span style="color: red;">attribute=</span><span style="color: blue;">"fieldSetName"</span> <span style="color: red;">value=</span><span style="color: blue;">"CloneAccount"</span><span style="color: navy; font-weight: bold;">></span>
<span style="color: navy; font-weight: bold;"></aura:set></aura:set></aura:component></span>
</pre>
</td></tr>
</tbody></table>
</div>
<b><br />CloneAccount Button</b><br />
We will need to bind this component to button<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/cloneComponent/cloneButton.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="457" data-original-width="800" height="364" src="https://www.springsoa.com/blogs/cloneComponent/cloneButton.png" width="640" /></a></div>
<br />
<br />
<br />
<b>CloneAccount FieldSet</b><br />
We will need to create the field set which will be used by clone base component on which fields to be copied.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/cloneComponent/cloneFieldSEt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="565" data-original-width="800" height="450" src="https://springsoa.com/blogs/cloneComponent/cloneFieldSEt.png" width="640" /></a></div>
<br />
<br />
In UI, we can see fields are cloned now:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/cloneComponent/cloneAccountUI.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="420" data-original-width="800" height="336" src="https://www.springsoa.com/blogs/cloneComponent/cloneAccountUI.png" width="640" /></a></div>
<br />
<br />
<br />
source code can be downloaded from <a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2E000003ZQzF&isdtp=p1">unmanaged </a>package or <a href="https://github.com/springsoa/springsoa-salesforce">github</a>Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com3tag:blogger.com,1999:blog-1459511178626634251.post-10212013220298362702019-11-10T14:41:00.001-08:002019-11-10T14:41:47.967-08:00Scheduling job every 5 minIn salesforce, with UI, we can schedule the job on daily bases, and using cron expression we can do hourly. There is no facility to run the job every minute, or every 5 minutes. However salesforce allows to start the job a few seconds from now or a few minutes from now. We can use that hack to run the job every 5 minutes.<br />
<br />
E.g. below : during the execute call, we can reschedule the job, which will abort existing job and create new job based on frequency provided.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 20em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa; font-style: italic;">/**</span>
<span style="color: #aaaaaa; font-style: italic;"> * Created by Chintan Shah on 11/10/2019.</span>
<span style="color: #aaaaaa; font-style: italic;"> */</span>
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">with</span> sharing <span style="color: #0000aa;">class</span> MasterScheduler <span style="color: #0000aa;">implements</span> Schedulable {
<span style="color: #0000aa;">private</span> <span style="color: #0000aa;">static</span> <span style="color: #0000aa;">final</span> Integer BATCH_FREQUENCY_IN_MINUTES = <span style="color: #009999;">5</span>; <span style="color: #aaaaaa; font-style: italic;">// ideally it should goto custom settings.</span>
<span style="color: #0000aa;">private</span> <span style="color: #0000aa;">static</span> <span style="color: #0000aa;">final</span> <span style="color: #00aaaa;">String</span> JOB_NAME = <span style="color: #aa5500;">'MasterScheduler'</span>;
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">void</span> execute(SchedulableContext schedulableContext) {
System.debug(<span style="color: #aa5500;">'MasterScheduler.execute : '</span>);
rescheduleJob( JOB_NAME, BATCH_FREQUENCY_IN_MINUTES );
}
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">static</span> <span style="color: #0000aa;">void</span> rescheduleJob(<span style="color: #00aaaa;">String</span> jobName, Integer batchFrequencyMinutes) {
System.debug(<span style="color: #aa5500;">'MasterScheduler.rescheduleJob : jobName '</span> + jobName + <span style="color: #aa5500;">' batchFrequencyMinutes '</span> + batchFrequencyMinutes );
abortJobByName(jobName);
Datetime dt = system.now().addMinutes(batchFrequencyMinutes);
<span style="color: #00aaaa;">String</span> cronExpression = <span style="color: #aa5500;">'0 '</span> + dt.minute() + <span style="color: #aa5500;">' '</span> + dt.hour() + <span style="color: #aa5500;">' '</span> + dt.day() + <span style="color: #aa5500;">' '</span> + dt.month() + <span style="color: #aa5500;">' ? '</span> + <span style="color: #aa5500;">' '</span> + dt.year();
System.debug(<span style="color: #aa5500;">'MasterScheduler.rescheduleJob : cronExpression '</span> + cronExpression );
System.schedule(jobName, cronExpression, <span style="color: #0000aa;">new</span> MasterScheduler());
}
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">static</span> <span style="color: #0000aa;">void</span> abortJobByName(<span style="color: #00aaaa;">String</span> jobName) {
System.debug(<span style="color: #aa5500;">'MasterScheduler.abortJobByName : jobName '</span> + jobName );
List<CronTrigger> cronTriggers = [select id, TimesTriggered, NextFireTime, CronExpression, PreviousFireTime, StartTime, EndTime from CronTrigger];
<span style="color: #0000aa;">for</span>(CronTrigger cronTrigger : cronTriggers ) {
abortJobById(cronTrigger.Id);
}
}
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">static</span> <span style="color: #0000aa;">void</span> abortJobById(<span style="color: #00aaaa;">String</span> jobId) {
System.debug(<span style="color: #aa5500;">'MasterScheduler.abortJobById : jobId '</span> + jobId );
System.abortJob(jobId);
}
}
</pre>
</div>
<br />
<br />
We can start the job from anonymous block:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 4em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #0000aa;">new</span> MasterScheduler().execute(<span style="color: #0000aa;">null</span>);
</pre>
</div>
<br />
This will run the job every 5 minutes. This is not best practice as we might be over using salesforce cloud resources, but it can come handy for certain situations.
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-61390572098035802632019-11-10T10:46:00.002-08:002019-11-10T10:46:40.154-08:00Sandbox RefreshUsually there are a lot of things needs to be done when doing sandbox refresh, e.g. activating uses, changing email address of contacts or other objects, etc.. We can plugin the sandbox refresh script mentioned <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_interface_System_SandboxPostCopy.htm">here</a>. However, we still have to write a lot of code or use tools to achieve many things needed depending on fullcopy or dev refresh.<br />
<div>
<br /></div>
<div>
We wrote framework to simplify the refresh of the sandbox, where we can add multiple plugins, and also added static resource configuration file which can help with plug in configuration.<br />
<br /></div>
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 5em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #007700;">public</span> <span style="color: #007700;">interface</span> <span style="color: #007700;">SandboxRefreshPlugin</span> {
String execute(SandboxRefreshTemplate sandboxRefreshTemplate);
}
</pre>
</td></tr>
</tbody></table>
</div>
<div>
<br /></div>
<div>
We also wrote multiple plugins to do the actual work, e.g.</div>
<div>
<pre style="background-color: white; font-family: Consolas; font-size: 9.8pt;"><ul>
<li>SandboxRefreshProvisionPlugin</li>
<ul>
<li>This will provision the users. </li>
<li>e.g. activate them, change their profiles</li>
<li>in static resource, it is under ProvisionUserRecords tag</li>
</ul>
<li>SandboxRefreshDataCreationPlugin</li>
<ul>
<li>This will create sample data after refresh (useful for dev sandbox)</li>
<li>In static resource it is under sObjectRecords records</li>
</ul>
<li>SandboxRefreshInvalidateEmailPlugin</li>
<ul>
<li>This will invalidate email addressed for specified fields</li>
<li>E.g. Contact or other email address that might be used for sending emails</li>
<li>In static resource, it is under inValidateEmailsObjects</li>
</ul>
<li>SandboxRefreshEncryptFieldsDataPlugin</li>
<ul>
<li>This will encrypt the data for specified field</li>
<li>E.g. some of the PII, PHI data needs to be encrypted</li>
<li>In static resource it is under encryptFieldsData</li>
</ul>
</ul>
</pre>
</div>
Configuration static resource would help the plugin to be more configurable. Hence, we created static resource file as below<br />
<br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 50em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85</pre>
</td><td><pre style="line-height: 125%; margin: 0;">{
<span style="color: blue;">"ProvisionUserRecords"</span>: [
{
<span style="color: blue;">"userName"</span>: <span style="color: blue;">"test@test.com.cshah"</span>,
<span style="color: blue;">"profile"</span>: <span style="color: blue;">"System Administrator"</span>,
<span style="color: blue;">"isActive"</span>: <span style="color: navy; font-weight: bold;">true</span>
}
],
<span style="color: blue;">"notificationEmails"</span>: <span style="color: blue;">"test@springsoa.com"</span>,
<span style="color: blue;">"inValidateEmailsObjects"</span>: [
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Contact"</span>,
<span style="color: blue;">"emailFields"</span>: <span style="color: blue;">"Email"</span>
},
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Lead"</span>,
<span style="color: blue;">"emailFields"</span>: <span style="color: blue;">"Email"</span>
},
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Case"</span>,
<span style="color: blue;">"emailFields"</span>: <span style="color: blue;">"SuppliedEmail"</span>
}
],
<span style="color: blue;">"encryptFieldsData"</span>: [
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Contact"</span>,
<span style="color: blue;">"sObjectFields"</span>: [
{
<span style="color: blue;">"fieldName"</span>: <span style="color: blue;">"AssistantPhone"</span>,
<span style="color: blue;">"format"</span>: <span style="color: blue;">"999{N}{N}{N}{N}{N}{N}{N}"</span>
},
{
<span style="color: blue;">"fieldName"</span>: <span style="color: blue;">"Email"</span>,
<span style="color: blue;">"format"</span>: <span style="color: blue;">"test@{S}{S}{S}{S}.com"</span>
}
]
}
],
<span style="color: blue;">"sObjectRecords"</span>: [
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Account"</span>,
<span style="color: blue;">"reference"</span>: <span style="color: blue;">"ISO_Account_1"</span>,
<span style="color: blue;">"attributes"</span>: {
<span style="color: blue;">"Name"</span>: <span style="color: blue;">"1 West Finance"</span>,
<span style="color: blue;">"Type"</span>: <span style="color: blue;">"ISO"</span>
}
},
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Account"</span>,
<span style="color: blue;">"reference"</span>: <span style="color: blue;">"Merchant_Account1"</span>,
<span style="color: blue;">"attributes"</span>: {
<span style="color: blue;">"Name"</span>: <span style="color: blue;">"The Carrington Group Limited"</span>,
<span style="color: blue;">"Type"</span>: <span style="color: blue;">"Merchant"</span>
}
},
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Opportunity"</span>,
<span style="color: blue;">"reference"</span>: <span style="color: blue;">"Opportunity1"</span>,
<span style="color: blue;">"attributes"</span>: {
<span style="color: blue;">"Name"</span>: <span style="color: blue;">"The Carrington Group Limited - 190529"</span>,
<span style="color: blue;">"AccountId"</span>: <span style="color: blue;">"reference-Merchant_Account1"</span>,
<span style="color: blue;">"StageName"</span>: <span style="color: blue;">"Approved"</span>,
<span style="color: blue;">"CloseDate"</span>: <span style="color: blue;">"10/24/2018"</span>
}
},
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Account"</span>,
<span style="color: blue;">"reference"</span>: <span style="color: blue;">"Merchant_Account2"</span>,
<span style="color: blue;">"attributes"</span>: {
<span style="color: blue;">"Name"</span>: <span style="color: blue;">"Otto Case"</span>,
<span style="color: blue;">"Type"</span>: <span style="color: blue;">"Merchant"</span>
}
},
{
<span style="color: blue;">"sObjectName"</span>: <span style="color: blue;">"Opportunity"</span>,
<span style="color: blue;">"reference"</span>: <span style="color: blue;">"Opportunity2"</span>,
<span style="color: blue;">"attributes"</span>: {
<span style="color: blue;">"Name"</span>: <span style="color: blue;">"Otto Case - 190307"</span>,
<span style="color: blue;">"AccountId"</span>: <span style="color: blue;">"reference-Merchant_Account2"</span>,
<span style="color: blue;">"StageName"</span>: <span style="color: blue;">"Approved"</span>,
<span style="color: blue;">"CloseDate"</span>: <span style="color: blue;">"10/24/2018"</span>
}
}
]
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
Most of the script initiates the batch file, as the full copy data could be quite huge. Now all we need to do is to specify the class <b>SandboxRefresh </b>during the refresh of the sandbox.<br />
<br />
Code can be installed using unmanaged package : <a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2E000003ZQyl" id="ExportPackageDetailPage:theForm:versionDetailBlock:j_id135:j_id136:pkgInstallUrl" name="ExportPackageDetailPage:theForm:versionDetailBlock:j_id135:j_id136:pkgInstallUrl" style="background-color: white; color: black; font-family: Arial, Helvetica, sans-serif; font-size: 12px;">https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2E000003ZQyl</a> or can be downloaded from github <a href="https://github.com/springsoa/springsoa-salesforce">https://github.com/springsoa/springsoa-salesforce</a><br />
<br />
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com1tag:blogger.com,1999:blog-1459511178626634251.post-29800245835386556372019-11-09T15:28:00.001-08:002019-11-09T15:29:25.027-08:00Large ModalWhen we create lightning component and associate with button, it has configurable height but width is always half of the screen.<br />
<br />
E.g. lightning component<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;"><!</span><span style="color: #007700;">--</span>
<span style="color: #007700;">-</span> <span style="color: #007700;">Created</span> <span style="color: #007700;">by</span> <span style="color: #007700;">Chintan</span> <span style="color: #007700;">Shah</span> <span style="color: #007700;">on</span> <span style="color: #007700;">10</span><span style="color: #333333;">/</span><span style="color: #007700;">20</span><span style="color: #333333;">/</span><span style="color: #007700;">2019</span><span style="color: #333333;">.</span>
<span style="color: #007700;">--</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">aura</span><span style="color: #555555; font-weight: bold;">:component</span> <span style="color: #007700;">description</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"DemoLargeModal"</span> <span style="color: #007700;">implements</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"force:lightningQuickActionWithoutHeader,force:hasRecordId"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><!</span><span style="color: #007700;">--ltng</span><span style="color: #555555; font-weight: bold;">:require</span> <span style="color: #007700;">styles</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"{!$Resource.largeModal}"</span> <span style="color: #333333;">/</span><span style="color: #007700;">--</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">section</span> <span style="color: #007700;">role</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"dialog"</span> <span style="color: #007700;">tabindex</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"-1"</span> <span style="color: #007700;">aria-labelledby</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-heading-01"</span> <span style="color: #007700;">aria-modal</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"true"</span> <span style="color: #007700;">aria-describedby</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-content-id-1"</span>
<span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal slds-fade-in-open slds-modal_large"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">div</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__container"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">header</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__header"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">lightning</span><span style="color: #555555; font-weight: bold;">:buttonIcon</span> <span style="color: #007700;">iconName</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"utility:close"</span>
<span style="color: #007700;">onclick</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"{! c.closeModel }"</span>
<span style="color: #007700;">alternativeText</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"close"</span>
<span style="color: #007700;">variant</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bare"</span>
<span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__close"</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">lightning</span><span style="color: #555555; font-weight: bold;">:buttonIcon</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">h2</span> <span style="color: #007700;">id</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-heading-01"</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-text-heading_medium slds-hyphenate"</span><span style="color: #333333;">></span> <span style="color: #007700;">Header</span> <span style="color: #333333;"></</span><span style="color: #007700;">h2</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">header</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">div</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__content slds-p-around_medium"</span> <span style="color: #007700;">id</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-content-id-1"</span><span style="color: #333333;">></span>
<span style="color: #007700;">Content</span>
<span style="color: #333333;"></</span><span style="color: #007700;">div</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">div</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">section</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">aura</span><span style="color: #555555; font-weight: bold;">:component</span><span style="color: #333333;">></span>
</pre>
</td></tr>
</tbody></table>
</div>
<br />
bound to button :<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/largeModal/largeModal1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="432" data-original-width="800" height="344" src="https://www.springsoa.com/blogs/largeModal/largeModal1.png" width="640" /></a></div>
<br />
<br />
The height is configuration but width is not, and upon clicking the button, it would show narrow modal.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/largeModal/largeModal3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="338" data-original-width="800" height="270" src="https://www.springsoa.com/blogs/largeModal/largeModal3.png" width="640" /></a></div>
<br />
<br />
<br />
We can fix this by adding stylesheet to our lightning component. However it needs to be added using stylesheet.<br />
<br />
Create CSS static resource:<br />
<br />
<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3
4</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #bb0066; font-weight: bold;">.slds-modal__container</span>{
<span style="color: #008800; font-weight: bold;">max-width</span><span style="color: #333333;">:</span> <span style="color: #6600ee; font-weight: bold;">70</span>rem <span style="color: #557799;">!important</span>;
<span style="color: #008800; font-weight: bold;">width</span><span style="color: #333333;">:</span><span style="color: #6600ee; font-weight: bold;">80</span><span style="color: #333333;">%</span> <span style="color: #557799;">!important</span>;
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<br />
Add the CSS in the lightning component:<br />
<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;"><!</span><span style="color: #007700;">--</span>
<span style="color: #007700;">-</span> <span style="color: #007700;">Created</span> <span style="color: #007700;">by</span> <span style="color: #007700;">Chintan</span> <span style="color: #007700;">Shah</span> <span style="color: #007700;">on</span> <span style="color: #007700;">10</span><span style="color: #333333;">/</span><span style="color: #007700;">20</span><span style="color: #333333;">/</span><span style="color: #007700;">2019</span><span style="color: #333333;">.</span>
<span style="color: #007700;">--</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">aura</span><span style="color: #555555; font-weight: bold;">:component</span> <span style="color: #007700;">description</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"DemoLargeModal"</span> <span style="color: #007700;">implements</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"force:lightningQuickActionWithoutHeader,force:hasRecordId"</span><span style="color: #333333;">></span>
<b> <span style="color: #333333;"><</span><span style="color: #007700;">ltng</span><span style="color: #555555;">:require</span> <span style="color: #007700;">styles</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"{!$Resource.largeModal}"</span> <span style="color: #333333;">/></span></b>
<span style="color: #333333;"><</span><span style="color: #007700;">section</span> <span style="color: #007700;">role</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"dialog"</span> <span style="color: #007700;">tabindex</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"-1"</span> <span style="color: #007700;">aria-labelledby</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-heading-01"</span> <span style="color: #007700;">aria-modal</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"true"</span> <span style="color: #007700;">aria-describedby</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-content-id-1"</span>
<span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal slds-fade-in-open slds-modal_large"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">div</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__container"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">header</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__header"</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">lightning</span><span style="color: #555555; font-weight: bold;">:buttonIcon</span> <span style="color: #007700;">iconName</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"utility:close"</span>
<span style="color: #007700;">onclick</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"{! c.closeModel }"</span>
<span style="color: #007700;">alternativeText</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"close"</span>
<span style="color: #007700;">variant</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bare"</span>
<span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__close"</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">lightning</span><span style="color: #555555; font-weight: bold;">:buttonIcon</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">h2</span> <span style="color: #007700;">id</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-heading-01"</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-text-heading_medium slds-hyphenate"</span><span style="color: #333333;">></span> <span style="color: #007700;">Header</span> <span style="color: #333333;"></</span><span style="color: #007700;">h2</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">header</span><span style="color: #333333;">></span>
<span style="color: #333333;"><</span><span style="color: #007700;">div</span> <span style="color: #007700;">class</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"slds-modal__content slds-p-around_medium"</span> <span style="color: #007700;">id</span><span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"modal-content-id-1"</span><span style="color: #333333;">></span>
<span style="color: #007700;">Content</span>
<span style="color: #333333;"></</span><span style="color: #007700;">div</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">div</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">section</span><span style="color: #333333;">></span>
<span style="color: #333333;"></</span><span style="color: #007700;">aura</span><span style="color: #555555; font-weight: bold;">:component</span><span style="color: #333333;">></span>
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<br />
Now modal shows up wide:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/largeModal/largeModal2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="333" data-original-width="800" height="266" src="https://www.springsoa.com/blogs/largeModal/largeModal2.png" width="640" /></a></div>
<br />
<br />
Source code can be found at : <a href="https://github.com/springsoa/springsoa-salesforce">https://github.com/springsoa/springsoa-salesforce</a>Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com2tag:blogger.com,1999:blog-1459511178626634251.post-42355969760838782672019-10-20T13:16:00.000-07:002019-10-20T13:16:10.921-07:00Email LogI know it has been around for a long time, but recently found this useful feature, where I can request the logs of all emails that has been sent. It can be super useful for troubleshooting.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/emailLog/emailLog.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="344" data-original-width="800" height="274" src="https://springsoa.com/blogs/emailLog/emailLog.png" width="640" /></a></div>
<br />
It would be nice to have some heroku app which can download this periodically and create cases for emails that are not delivered.Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com2tag:blogger.com,1999:blog-1459511178626634251.post-53155598205699538152019-10-20T12:30:00.000-07:002019-10-20T12:54:04.263-07:00Mock FrameworkAs Salesforce has introduced powerful <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_stub_api.htm">mock framework</a> for testing, we wrote thin layer on top of it to take full advantage of the dynamic mock class that you can create.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/rcRo-6dQzMo/0.jpg" src="https://www.youtube.com/embed/rcRo-6dQzMo?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
<br />
It is always a good practice to break down your classes, and methods into small chunks, and mostly take further reusable methods in services. However, at the time of testing, it gets hard to test your code if it relies on too many services method. You have to have to knowledge of the service.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/MockIt/Slide2.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="800" height="360" src="https://springsoa.com/blogs/MockIt/Slide2.JPG" width="640" /></a></div>
In code, it may look like<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">10</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">18</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> this <span style="color: black; font-weight: bold;">is</span> the service that will be called by caller
<span style="color: #333333;">*</span> it would be faked out during the testing framework<span style="color: #333333;">.</span>
<span style="color: #333333;">*/</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">MockDemoService</span> {
public List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> toUpper(List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> inputs) {
List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> outputs <span style="color: #333333;">=</span> new List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span>();
<span style="color: #008800; font-weight: bold;">for</span>(String <span style="color: #007020;">input</span> : inputs ) {
outputs<span style="color: #333333;">.</span>add( <span style="color: #007020;">input</span><span style="color: #333333;">.</span>toUpperCase() );
}
<span style="color: #008800; font-weight: bold;">return</span> outputs;
}
public String concat(List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> inputs) {
String output <span style="color: #333333;">=</span> null;
<span style="color: #008800; font-weight: bold;">for</span>(String <span style="color: #007020;">input</span> : inputs ) {
output <span style="color: #333333;">=</span> ( output <span style="color: #333333;">==</span> null <span style="background-color: #ffaaaa; color: red;">?</span> <span style="background-color: #fff0f0;">''</span> : ( output <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' '</span> ) ) <span style="color: #333333;">+</span> <span style="color: #007020;">input</span>;
}
<span style="color: #008800; font-weight: bold;">return</span> output;
}
}
</pre>
</div>
<br />
<br />
And caller<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">10</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">18</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> demo of how to use this framework<span style="color: #333333;">.</span>
<span style="color: #333333;">*</span>
<span style="color: #333333;">*</span> MockDemoServiceCaller <span style="color: #333333;">-></span> MockDemoService
<span style="color: #333333;">*</span>
<span style="color: #333333;">*</span> During testing, we will just mock the entire MockDemoService
<span style="color: #333333;">*</span>
<span style="color: #333333;">*/</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">MockDemoServiceCaller</span> {
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> instance of mockDemoService
<span style="color: #333333;">*/</span>
public MockDemoService mockDemoService;
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> regular constructor
<span style="color: #333333;">*/</span>
public MockDemoServiceCaller() {
mockDemoService <span style="color: #333333;">=</span> new MockDemoService();
}
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> entry point <span style="color: #008800; font-weight: bold;">for</span> fake service
<span style="color: #333333;">*</span>
<span style="color: #333333;">*</span> <span style="color: #555555; font-weight: bold;">@param</span>
<span style="color: #333333;">*/</span>
public MockDemoServiceCaller(MockDemoService mockDemoService) {
this<span style="color: #333333;">.</span>mockDemoService <span style="color: #333333;">=</span> mockDemoService;
}
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> sample routine that we are going to test<span style="color: #333333;">.</span>
<span style="color: #333333;">*/</span>
public String sampleRoutine(List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> inputs) {
<span style="color: #008800; font-weight: bold;">try</span> {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' MockDemoServiceCaller.sampleRoutine inputs '</span> <span style="color: #333333;">+</span> JSON<span style="color: #333333;">.</span>serialize(inputs) );
List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> outputs <span style="color: #333333;">=</span> mockDemoService<span style="color: #333333;">.</span>toUpper( new List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> { <span style="background-color: #fff0f0;">'John'</span>, <span style="background-color: #fff0f0;">'Doe'</span> } );
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' MockDemoServiceCaller.sampleRoutine outputs '</span> <span style="color: #333333;">+</span> JSON<span style="color: #333333;">.</span>serialize(outputs) );
String output <span style="color: #333333;">=</span> mockDemoService<span style="color: #333333;">.</span>concat( outputs );
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' MockDemoServiceCaller.sampleRoutine output '</span> <span style="color: #333333;">+</span> output );
<span style="color: #008800; font-weight: bold;">return</span> output;
} catch(<span style="color: red; font-weight: bold;">Exception</span> e) {
throw new MockException(<span style="background-color: #fff0f0;">'What</span><span style="background-color: #fff0f0; color: #666666; font-weight: bold;">\'</span><span style="background-color: #fff0f0;"> up? '</span> <span style="color: #333333;">+</span> e<span style="color: #333333;">.</span>getStackTraceString() );
}
}
}
</pre>
</div>
<br />
Now when we write the test class for MockDemoServiceCaller, it would be good to have full control over the output of mock demo service. We should be able to return various result including the exception. Salesforce mock framework allows you to mock the service so you have full control when you write the test class for service caller.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/MockIt/Slide4.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="800" height="360" src="https://springsoa.com/blogs/MockIt/Slide4.JPG" width="640" /></a></div>
<br />
<br />
Here is example of the code. You can see in line 19 and 20, we create dynamic service instance using mock framework, as well as send mock individual method and return the response.<br />
<br />
On line 34, we can see, we can even return the exception in the dynamic mock instance that we create, and hence we have full control on how to test the service caller.<br />
<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">10</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">18</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
<span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> test <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">for</span> MockDemoServiceCaller
<span style="color: #333333;">*/</span>
<span style="color: #555555; font-weight: bold;">@isTest</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">MockDemoServiceCallerTest</span> {
<span style="color: #555555; font-weight: bold;">@testSetup</span>
public static void testSetup() {
}
<span style="color: #555555; font-weight: bold;">@isTest</span>
public static void testSampleRoutine1() {
MockProvider mockDeMockServiceProvider <span style="color: #333333;">=</span> new MockProvider( MockDemoService<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">class</span> <span style="background-color: #ffaaaa; color: red;">);</span>
MockDemoService mockDemoService <span style="color: #333333;">=</span> (MockDemoService) mockDeMockServiceProvider<span style="color: #333333;">.</span>getMock();
<b> mockDeMockServiceProvider<span style="color: #333333;">.</span>addReturnValue( <span style="background-color: #fff0f0;">'toUpper'</span>, new List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> { <span style="background-color: #fff0f0;">'HELLO'</span>, <span style="background-color: #fff0f0;">'WORLD'</span> } );
mockDeMockServiceProvider<span style="color: #333333;">.</span>addReturnValue( <span style="background-color: #fff0f0;">'concat'</span>, <span style="background-color: #fff0f0;">'HELLO TEST'</span> );
</b> <span style="color: #333333;">//</span> initialize MockDemoServiceCaller <span style="color: #008800; font-weight: bold;">with</span> mock MockDemoService
MockDemoServiceCaller mockDemoServiceCaller <span style="color: #333333;">=</span> new MockDemoServiceCaller( mockDemoService );
String output <span style="color: #333333;">=</span> mockDemoServiceCaller<span style="color: #333333;">.</span>sampleRoutine( new List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> { <span style="background-color: #fff0f0;">'John'</span>, <span style="background-color: #fff0f0;">'Doe'</span>} );
System<span style="color: #333333;">.</span>assertEquals( output, <span style="background-color: #fff0f0;">'HELLO TEST'</span>, <span style="background-color: #fff0f0;">'It should be HELLO TEST based on mock '</span>);
}
<span style="color: #555555; font-weight: bold;">@isTest</span>
public static void testSampleRoutine2() {
MockProvider mockDeMockServiceProvider <span style="color: #333333;">=</span> new MockProvider( MockDemoService<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">class</span> <span style="background-color: #ffaaaa; color: red;">);</span>
MockDemoService mockDemoService <span style="color: #333333;">=</span> (MockDemoService) mockDeMockServiceProvider<span style="color: #333333;">.</span>getMock();
<b> mockDeMockServiceProvider<span style="color: #333333;">.</span>addReturnValue( <span style="background-color: #fff0f0;">'toUpper'</span>, new List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> { <span style="background-color: #fff0f0;">'HELLO'</span>, <span style="background-color: #fff0f0;">'WORLD'</span> } );
mockDeMockServiceProvider<span style="color: #333333;">.</span>addReturnValue( <span style="background-color: #fff0f0;">'concat'</span>, new MockException(<span style="background-color: #fff0f0;">'Test Exception'</span>) );
</b> <span style="color: #333333;">//</span> initialize MockDemoServiceCaller <span style="color: #008800; font-weight: bold;">with</span> mock MockDemoService
MockDemoServiceCaller mockDemoServiceCaller <span style="color: #333333;">=</span> new MockDemoServiceCaller( mockDemoService );
<span style="color: #008800; font-weight: bold;">try</span> {
String output <span style="color: #333333;">=</span> mockDemoServiceCaller<span style="color: #333333;">.</span>sampleRoutine( new List<span style="color: #333333;"><</span>String<span style="color: #333333;">></span> { <span style="background-color: #fff0f0;">'John'</span>, <span style="background-color: #fff0f0;">'Doe'</span>} );
System<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">assert</span>( false, <span style="background-color: #fff0f0;">'Exception was expected, success was not expected '</span>);
} catch(<span style="color: red; font-weight: bold;">Exception</span> e) {
System<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">assert</span>( true, <span style="background-color: #fff0f0;">'Exception was expected. '</span>);
}
}
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
With wrapper around salesforce framework, we don't need to write individual mock classes, but we can declarative use what to mock and what it would return.<br />
<br />
<br />
<b>Web Service Mock</b><br />
Web service mock framework has been around for years, however we have to write mock class for each web service we are trying to call. With this framework, we can declarative create dynamic instance of the mock class and test the class which is calling web service. e.g. below<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/MockIt/Slide3.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="800" height="360" src="https://springsoa.com/blogs/MockIt/Slide3.JPG" width="640" /></a></div>
<br />
<br />
<br />
<b>Actual Class</b><br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">10</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">19</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">MockDemoWebServiceCaller</span> {
public List<span style="color: #333333;"><</span>Map<span style="color: #333333;"><</span>String,Object<span style="color: #333333;">>></span> httpGetCallout(String url) {
List<span style="color: #333333;"><</span>Map<span style="color: #333333;"><</span>String,Object<span style="color: #333333;">>></span> returnEmployees <span style="color: #333333;">=</span> new List<span style="color: #333333;"><</span>Map<span style="color: #333333;"><</span>String,Object<span style="color: #333333;">>></span>();
Http http <span style="color: #333333;">=</span> new Http();
HttpRequest request <span style="color: #333333;">=</span> new HttpRequest();
request<span style="color: #333333;">.</span>setEndpoint( url );
request<span style="color: #333333;">.</span>setMethod(<span style="background-color: #fff0f0;">'GET'</span>);
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' MockDemoWebServiceCaller.httpGetCallout url '</span> <span style="color: #333333;">+</span> url );
HttpResponse response <span style="color: #333333;">=</span> http<span style="color: #333333;">.</span>send(request);
<span style="color: #008800; font-weight: bold;">if</span> (response<span style="color: #333333;">.</span>getStatusCode() <span style="color: #333333;">==</span> <span style="color: #0000dd; font-weight: bold;">200</span>) {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">' response.getBody() '</span> <span style="color: #333333;">+</span> response<span style="color: #333333;">.</span>getBody() );
List<span style="color: #333333;"><</span>Object<span style="color: #333333;">></span> employees <span style="color: #333333;">=</span> (List<span style="color: #333333;"><</span>Object<span style="color: #333333;">></span>) JSON<span style="color: #333333;">.</span>deserializeUntyped( response<span style="color: #333333;">.</span>getBody() );
<span style="color: #008800; font-weight: bold;">for</span>(Object employee : employees ) {
Map<span style="color: #333333;"><</span>String, Object<span style="color: #333333;">></span> currentEmployee <span style="color: #333333;">=</span> ( Map<span style="color: #333333;"><</span>String, Object<span style="color: #333333;">></span> ) ( employee );
returnEmployees<span style="color: #333333;">.</span>add( currentEmployee );
}
} <span style="color: #008800; font-weight: bold;">else</span> {
throw new MockException( response<span style="color: #333333;">.</span>getStatusCode() <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' '</span> <span style="color: #333333;">+</span> response<span style="color: #333333;">.</span>getBody() );
}
system<span style="color: #333333;">.</span>debug( <span style="background-color: #fff0f0;">' MockDemoWebServiceCaller.httpGetCallout returnEmployees '</span> <span style="color: #333333;">+</span> returnEmployees );
<span style="color: #008800; font-weight: bold;">return</span> returnEmployees;
}
public List<span style="color: #333333;"><</span>Map<span style="color: #333333;"><</span>String,Object<span style="color: #333333;">>></span> getAllEmployees() {
<span style="color: #008800; font-weight: bold;">try</span> {
<span style="color: #008800; font-weight: bold;">return</span> httpGetCallout(<span style="background-color: #fff0f0;">'https://basic-authentication-ws.herokuapp.com/hr/employees'</span>);
} catch(<span style="color: red; font-weight: bold;">Exception</span> e) {
throw new MockException(e);
}
}
public List<span style="color: #333333;"><</span>Map<span style="color: #333333;"><</span>String,Object<span style="color: #333333;">>></span> getEmployee(String column, String value) {
<span style="color: #008800; font-weight: bold;">try</span> {
<span style="color: #008800; font-weight: bold;">return</span> httpGetCallout(<span style="background-color: #fff0f0;">'https://basic-authentication-ws.herokuapp.com/hr/employee/'</span> <span style="color: #333333;">+</span> column <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'/'</span> <span style="color: #333333;">+</span> value );
} catch(<span style="color: red; font-weight: bold;">Exception</span> e) {
throw new MockException(e);
}
}
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<br />
<b>Test Class</b><br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 14em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by Chintan Shah on <span style="color: #0000dd; font-weight: bold;">10</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">19</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2019.</span>
<span style="color: #333333;">*/</span>
<span style="color: #555555; font-weight: bold;">@isTest</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">MockDemoWebServiceCallerTest</span> {
<span style="color: #555555; font-weight: bold;">@testSetup</span>
public static void testSetup() {
}
<span style="color: #555555; font-weight: bold;">@isTest</span>
public static void testGetAllEmployees() {
MockHttpCallout mockHttpCallout <span style="color: #333333;">=</span> new MockHttpCallout();
mockHttpCallout<span style="color: #333333;">.</span>setMock();
mockHttpCallout<span style="color: #333333;">.</span>addJsonHttpResponse(<span style="background-color: #fff0f0;">'https://basic-authentication-ws.herokuapp.com/hr/employees'</span>, <span style="background-color: #fff0f0;">'[{"ExternalId":"E-001","Name":"Chintan Shah","DisplayUrl":"https://external.com/E-001","EmployeeAccountKey":"Account-001"},{"ExternalId":"E-002","Name":"Chuck Summer","DisplayUrl":"https://external.com/E-002","EmployeeAccountKey":"Account-001"},{"ExternalId":"E-003","Name":"Chris Walker","DisplayUrl":"https://external.com/E-003","EmployeeAccountKey":"Account-001"},{"ExternalId":"E-004","Name":"Dan Topper","DisplayUrl":"https://external.com/E-004","EmployeeAccountKey":"Account-002"},{"ExternalId":"E-005","Name":"Drew Berry","DisplayUrl":"https://external.com/E-005","EmployeeAccountKey":"Account-002"},{"ExternalId":"E-006","Name":"Don Aiken","DisplayUrl":"https://external.com/E-006","EmployeeAccountKey":"Account-002"}]'</span>);
System<span style="color: #333333;">.</span>assertEquals( <span style="color: #0000dd; font-weight: bold;">6</span>, new MockDemoWebServiceCaller()<span style="color: #333333;">.</span>getAllEmployees()<span style="color: #333333;">.</span>size(), <span style="background-color: #fff0f0;">'Should be 6'</span>);
}
<span style="color: #555555; font-weight: bold;">@isTest</span>
public static void testGetEmployee() {
<span style="color: #333333;">//</span>
MockHttpCallout mockHttpCallout <span style="color: #333333;">=</span> new MockHttpCallout();
mockHttpCallout<span style="color: #333333;">.</span>setMock();
<b> mockHttpCallout<span style="color: #333333;">.</span>addJsonHttpResponse(<span style="background-color: #fff0f0;">'https://basic-authentication-ws.herokuapp.com/hr/employee/Name/Chintan'</span>, <span style="background-color: #fff0f0;">'[{"ExternalId":"E-001","Name":"Chintan Shah","DisplayUrl":"https://external.com/E-001","EmployeeAccountKey":"Account-001"}]'</span>);
</b> System<span style="color: #333333;">.</span>assertEquals( <span style="color: #0000dd; font-weight: bold;">1</span>, new MockDemoWebServiceCaller()<span style="color: #333333;">.</span>getEmployee(<span style="background-color: #fff0f0;">'Name'</span>,<span style="background-color: #fff0f0;">'Chintan'</span>)<span style="color: #333333;">.</span>size() , <span style="background-color: #fff0f0;">'Should be 1'</span>);
}
<span style="color: #555555; font-weight: bold;">@isTest</span>
public static void testGetAllEmployees2() {
MockHttpCallout mockHttpCallout <span style="color: #333333;">=</span> new MockHttpCallout();
mockHttpCallout<span style="color: #333333;">.</span>setMock();
<b> mockHttpCallout<span style="color: #333333;">.</span>addErrorResponse(<span style="background-color: #fff0f0;">'https://basic-authentication-ws.herokuapp.com/hr/employees'</span>, <span style="color: #0000dd;">401</span> );
</b> <span style="color: #008800; font-weight: bold;">try</span> {
new MockDemoWebServiceCaller()<span style="color: #333333;">.</span>getAllEmployees()<span style="color: #333333;">.</span>size();
System<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">assert</span>( false, <span style="background-color: #fff0f0;">'Exception was expected, success was not expected '</span>);
} catch(<span style="color: red; font-weight: bold;">Exception</span> e) {
System<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">assert</span>( true, <span style="background-color: #fff0f0;">'Exception was expected. '</span>);
}
}
<span style="color: #555555; font-weight: bold;">@isTest</span>
public static void testGetEmployee2() {
<span style="color: #333333;">//</span>
MockHttpCallout mockHttpCallout <span style="color: #333333;">=</span> new MockHttpCallout();
mockHttpCallout<span style="color: #333333;">.</span>setMock();
mockHttpCallout<span style="color: #333333;">.</span>addErrorResponse(<span style="background-color: #fff0f0;">'https://basic-authentication-ws.herokuapp.com/hr/employee/Name/Chintan'</span>, <span style="color: #0000dd; font-weight: bold;">401</span>);
<span style="color: #008800; font-weight: bold;">try</span> {
new MockDemoWebServiceCaller()<span style="color: #333333;">.</span>getEmployee(<span style="background-color: #fff0f0;">'Name'</span>,<span style="background-color: #fff0f0;">'Chintan'</span>)<span style="color: #333333;">.</span>size();
System<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">assert</span>( false, <span style="background-color: #fff0f0;">'Exception was expected, success was not expected '</span>);
} catch(<span style="color: red; font-weight: bold;">Exception</span> e) {
System<span style="color: #333333;">.</span><span style="color: #008800; font-weight: bold;">assert</span>( true, <span style="background-color: #fff0f0;">'Exception was expected. '</span>);
}
}
}
</pre>
</td></tr>
</tbody></table>
</div>
<br />
<br />
As you can see, we don't have to write the mock class for each web service, we simply just need to use the http mock callout and set the response as mentioned in line 25 and 33.<br />
<br />
You can download the code from : <a href="https://github.com/springsoa/springsoa-salesforce">https://github.com/springsoa/springsoa-salesforce</a><br />
or from unmanaged package <a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2E000003ZQaK">here</a><br />
<br />
<br />
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-35518530392706244372019-10-10T07:52:00.003-07:002019-10-10T09:15:42.869-07:00External Data ServiceAs mentioned in my previous blog : <a href="https://chintanblog.blogspot.com/2017/05/odataheroku-with-salesforce-integrate.html">https://chintanblog.blogspot.com/2017/05/odataheroku-with-salesforce-integrate.html</a> , if we can expose the external data as OData 2.0 or 4.0, we can directly consume them in salesforce as External Objects. The second option would be to directly consume those web service using External Data Service and consume them as external objects.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/External.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="449" data-original-width="800" height="359" src="https://springsoa.com/blogs/external/External.png" width="640" /></a></div>
<br />
<br />
As you can see above, either you can put thin layer against external web service to do protocol conversion to support Odata (as mentioned in <a href="https://chintanblog.blogspot.com/2017/05/odataheroku-with-salesforce-integrate.html">https://chintanblog.blogspot.com/2017/05/odataheroku-with-salesforce-integrate.html</a>) or write the plugin in Apex.<br />
<br />
Here we will discuss how to write External Data Service in Apex.<br />
<br />
1) I created a sample external web service to just for the demo - It is HR service, which return list of employees, and also employees by name, account, etc..<br />
<br />
<a href="https://github.com/c-shah/basic-authentication">https://github.com/c-shah/basic-authentication</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/WS1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="378" data-original-width="800" height="302" src="https://springsoa.com/blogs/external/WS1.png" width="640" /></a></div>
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/WS2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="391" data-original-width="795" height="314" src="https://springsoa.com/blogs/external/WS2.png" width="640" /></a></div>
<br />
<br />
2) As next step, we need to write<br />
<br />
ExternalDataSourceProvider -> ExternalDataSourceConnection -> ExternalDataService<br />
<br />
a) ExternalDataSourceProvider<br />
<b><br /></b>
this class basically extends <span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;">DataSource.Provider, and provides what capability are supported from both authentication and database support (e.g. query, update)</span><br />
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 20em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa; font-style: italic;">/**</span>
<span style="color: #aaaaaa; font-style: italic;"> * Created by chint on 10/4/2019.</span>
<span style="color: #aaaaaa; font-style: italic;"> */</span>
global without sharing <span style="color: #0000aa;">class</span> <b>ExternalDataSourceProvider </b><span style="color: #0000aa;">extends</span> DataSource.Provider {
override global List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() {
System.debug(<span style="color: #aa5500;">' ExternalDataSourceProvider.getAuthenticationCapabilities '</span>);
List<DataSource.AuthenticationCapability> capabilities = <span style="color: #0000aa;">new</span> List<DataSource.AuthenticationCapability> {
DataSource.AuthenticationCapability.ANONYMOUS
};
System.debug(<span style="color: #aa5500;">' ExternalDataSourceProvider.getAuthenticationCapabilities '</span> + JSON.serialize(capabilities) );
<span style="color: #0000aa;">return</span> capabilities;
}
override global List<DataSource.Capability> getCapabilities() {
System.debug(<span style="color: #aa5500;">' ExternalDataSourceProvider.getCapabilities '</span>);
List<DataSource.Capability> capabilities = <span style="color: #0000aa;">new</span> List<DataSource.Capability> {
DataSource.Capability.ROW_QUERY
};
System.debug(<span style="color: #aa5500;">' ExternalDataSourceProvider.getCapabilities '</span> + JSON.serialize(capabilities) );
<span style="color: #0000aa;">return</span> capabilities;
}
override global DataSource.Connection getConnection(DataSource.ConnectionParams connectionParams) {
System.debug(<span style="color: #aa5500;">' ExternalDataSourceProvider.getConnection connectionParams: '</span> + connectionParams);
DataSource.Connection connection = <span style="color: #0000aa;">new</span> ExternalDataSourceConnection(connectionParams);
System.debug(<span style="color: #aa5500;">' ExternalDataSourceProvider.getConnection connection: '</span> + connection);
<span style="color: #0000aa;">return</span> connection;
}
}
</pre>
</div>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
b) ExternalDataSourceConnection<br />
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
This class extends <span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;">DataSource.Connection, and responsible for providing table structures and as well as data results which will be consumed by query associated with External Objects</span><br />
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 20em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa; font-style: italic;">/**</span>
<span style="color: #aaaaaa; font-style: italic;"> * Created by chint on 10/4/2019.</span>
<span style="color: #aaaaaa; font-style: italic;"> */</span>
global without sharing <span style="color: #0000aa;">class</span> <b>ExternalDataSourceConnection </b><span style="color: #0000aa;">extends</span> DataSource.Connection {
<span style="color: #0000aa;">private</span> DataSource.ConnectionParams connectionInfo ;
global ExternalDataSourceConnection(DataSource.ConnectionParams connectionInfo) {
System.debug(<span style="color: #aa5500;">' ExternalDataSourceConnection.ExternalDataSourceConnection connectionInfo: '</span> + JSON.serialize(connectionInfo));
<span style="color: #0000aa;">this</span>.connectionInfo = connectionInfo;
}
override global List<DataSource.Table> sync() {
System.debug(<span style="color: #aa5500;">' ExternalDataSourceConnection.sync '</span>);
List<DataSource.Table> tables = ExternalDataService.getInstance().getTables();
System.debug(<span style="color: #aa5500;">' ExternalDataSourceConnection.sync tables '</span> + JSON.serialize(tables) );
<span style="color: #0000aa;">return</span> tables;
}
override global DataSource.TableResult query(DataSource.QueryContext context) {
<span style="color: #0000aa;">try</span> {
printContent(context);
DataSource.TableResult tableResult = DataSource.TableResult.get(context, ExternalDataService.getInstance().getData(context) );
System.debug(<span style="color: #aa5500;">' ExternalDataSourceConnection.query tableResult '</span> + JSON.serialize(tableResult));
<span style="color: #0000aa;">return</span> tableResult;
} <span style="color: #0000aa;">catch</span> (Exception currentException) {
<span style="color: #00aaaa;">String</span> message = currentException.getMessage() + currentException.getStackTraceString() ;
System.debug(<span style="color: #aa5500;">' ExternalDataSourceConnection.query exception : '</span> + message );
<span style="color: #0000aa;">throw</span> <span style="color: #0000aa;">new</span> DataSource.DataSourceException(message);
}
}
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">static</span> <span style="color: #0000aa;">void</span> printContent(DataSource.QueryContext context) {
System.debug(<span style="color: #aa5500;">' ExternalDataSourceConnection.printContent queryMoreToken '</span> + context.queryMoreToken + <span style="color: #aa5500;">' tableSelected '</span> + context.tableSelection.tableSelected );
DataSource.Filter filter = context.tableSelection.filter;
List<DataSource.Order> orders = context.tableSelection.order;
List<DataSource.ColumnSelection> columnsSelected = context.tableSelection.columnsSelected;
<span style="color: #0000aa;">if</span>( filter != <span style="color: #0000aa;">null</span> ) {
System.debug(<span style="color: #aa5500;">'ExternalDataSourceConnection.printContent filter columnName '</span> + filter.columnName + <span style="color: #aa5500;">' columnValue '</span> + filter.columnValue + <span style="color: #aa5500;">' subfilters '</span> + filter.subfilters + <span style="color: #aa5500;">' tableName '</span> + filter.tableName + <span style="color: #aa5500;">' type '</span> + filter.type );
}
<span style="color: #0000aa;">if</span>( orders != <span style="color: #0000aa;">null</span> ) {
<span style="color: #0000aa;">for</span>(DataSource.Order order : orders ) {
System.debug(<span style="color: #aa5500;">'ExternalDataSourceConnection.printContent order columnName '</span> + order.columnName + <span style="color: #aa5500;">' tableName '</span> + order.tableName + <span style="color: #aa5500;">' direction '</span> + order.direction );
}
}
<span style="color: #0000aa;">if</span>( columnsSelected != <span style="color: #0000aa;">null</span> ) {
<span style="color: #0000aa;">for</span>(DataSource.ColumnSelection columnSelected : columnsSelected ) {
System.debug(<span style="color: #aa5500;">'ExternalDataSourceConnection.printContent columnSelected columnName '</span> + columnSelected.columnName + <span style="color: #aa5500;">' tableName '</span> + columnSelected.tableName + <span style="color: #aa5500;">' aggregation '</span> + columnSelected.aggregation );
}
}
}
}
</pre>
</div>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;"><br /></span>
<span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;">3) ExternalDataService, which takes care of calling web service and returning the data needed for </span>ExternalDataSourceConnection.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; height: 20em; overflow-y: scroll; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa; font-style: italic;">/**</span>
<span style="color: #aaaaaa; font-style: italic;"> * Created by chint on 10/4/2019.</span>
<span style="color: #aaaaaa; font-style: italic;"> */</span>
<span style="color: #0000aa;">public</span> without sharing <span style="color: #0000aa;">class</span> <b>ExternalDataService </b>{
<span style="color: #0000aa;">private</span> <span style="color: #0000aa;">static</span> ExternalDataService instance;
<span style="color: #0000aa;">private</span> ExternalDataService() {
System.debug(<span style="color: #aa5500;">' ExternalDataService.ExternalDataService '</span>);
}
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">static</span> ExternalDataService getInstance() {
System.debug(<span style="color: #aa5500;">' ExternalDataService.getInstance '</span>);
<span style="color: #0000aa;">if</span>( instance == <span style="color: #0000aa;">null</span> ) {
instance = <span style="color: #0000aa;">new</span> ExternalDataService();
}
<span style="color: #0000aa;">return</span> instance;
}
<span style="color: #0000aa;">public</span> List<DataSource.Table> getTables() {
System.debug(<span style="color: #aa5500;">' ExternalDataService.getTables '</span>);
List<DataSource.Table> tables = <span style="color: #0000aa;">new</span> List<DataSource.Table> {
getEmployeeTable(),
getAddressTable() };
System.debug(<span style="color: #aa5500;">' ExternalDataService.getTables tables '</span> + JSON.serialize(tables) );
<span style="color: #0000aa;">return</span> tables;
}
<span style="color: #0000aa;">public</span> List<Map<<span style="color: #00aaaa;">String</span>, <span style="color: #00aaaa;">Object</span>>> getData(DataSource.QueryContext context) {
System.debug(<span style="color: #aa5500;">' ExternalDataService.getData context '</span> + context );
<span style="color: #0000aa;">return</span> getEmployeeData(context);
}
<span style="color: #0000aa;">private</span> DataSource.Table getEmployeeTable() {
List<DataSource.Column> columns;
columns = <span style="color: #0000aa;">new</span> List<DataSource.Column>();
columns.add(DataSource.Column.text(<span style="color: #aa5500;">'ExternalId'</span>, <span style="color: #009999;">255</span>));
columns.add(DataSource.Column.text(<span style="color: #aa5500;">'Name'</span>, <span style="color: #009999;">255</span>));
columns.add(DataSource.Column.url(<span style="color: #aa5500;">'DisplayUrl'</span>));
columns.add(DataSource.Column.indirectLookup(<span style="color: #aa5500;">'EmployeeAccountKey'</span>, <span style="color: #aa5500;">'Account'</span>, <span style="color: #aa5500;">'Account_Key__c'</span>));
<span style="color: #0000aa;">return</span> DataSource.Table.get(<span style="color: #aa5500;">'Employee'</span>, <span style="color: #aa5500;">'ExternalId'</span>, columns);
}
<span style="color: #0000aa;">private</span> List<Map<<span style="color: #00aaaa;">String</span>, <span style="color: #00aaaa;">Object</span>>> getDummyData() {
Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>> row1 = <span style="color: #0000aa;">new</span> Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>> {
<span style="color: #aa5500;">'ExternalId'</span> => <span style="color: #aa5500;">'Emp1'</span>,
<span style="color: #aa5500;">'Name'</span> => <span style="color: #aa5500;">'Chintan Shah'</span>,
<span style="color: #aa5500;">'EmployeeAccountKey'</span> => <span style="color: #aa5500;">'ACN1'</span>
};
Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>> row2 = <span style="color: #0000aa;">new</span> Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>> {
<span style="color: #aa5500;">'ExternalId'</span> => <span style="color: #aa5500;">'Emp2'</span>,
<span style="color: #aa5500;">'Name'</span> => <span style="color: #aa5500;">'Mark Twain'</span>,
<span style="color: #aa5500;">'EmployeeAccountKey'</span> => <span style="color: #aa5500;">'ACN1'</span>
};
Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>> row3 = <span style="color: #0000aa;">new</span> Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>> {
<span style="color: #aa5500;">'ExternalId'</span> => <span style="color: #aa5500;">'Emp3'</span>,
<span style="color: #aa5500;">'Name'</span> => <span style="color: #aa5500;">'John Doe'</span>,
<span style="color: #aa5500;">'EmployeeAccountKey'</span> => <span style="color: #aa5500;">'ACN2'</span>
};
List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>> dataRows = <span style="color: #0000aa;">new</span> List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>> { row1, row2, row3 };
<span style="color: #0000aa;">return</span> dataRows;
}
<span style="color: #0000aa;">private</span> List<Map<<span style="color: #00aaaa;">String</span>, <span style="color: #00aaaa;">Object</span>>> getEmployeeData(DataSource.QueryContext context) {
<span style="color: #0000aa;">if</span>( context.tableSelection != <span style="color: #0000aa;">null</span> && context.tableSelection.tableSelected == <span style="color: #aa5500;">'Employee'</span> ) {
DataSource.Filter filter = context.tableSelection.filter;
<span style="color: #00aaaa;">String</span> url = <span style="color: #aa5500;">'/hr/employees'</span>;
<span style="color: #0000aa;">if</span>( filter != <span style="color: #0000aa;">null</span> ) {
url = <span style="color: #aa5500;">'/hr/employee/'</span> + filter.columnName + <span style="color: #aa5500;">'/'</span> + filter.columnValue;
}
List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>> response = httpGetCallout(<span style="color: #aa5500;">'HerokuBasicAuth'</span>, url);
<span style="color: #0000aa;">return</span> response;
}
<span style="color: #0000aa;">return</span> <span style="color: #0000aa;">new</span> List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>>();
}
<span style="color: #0000aa;">public</span> <span style="color: #0000aa;">static</span> List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>> httpGetCallout(<span style="color: #00aaaa;">String</span> namedCredentials, <span style="color: #00aaaa;">String</span> url) {
List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>> returnEmployees = <span style="color: #0000aa;">new</span> List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>>();
Http http = <span style="color: #0000aa;">new</span> Http();
HttpRequest request = <span style="color: #0000aa;">new</span> HttpRequest();
request.setEndpoint(<span style="color: #aa5500;">'callout:'</span> + namedCredentials + url );
request.setMethod(<span style="color: #aa5500;">'GET'</span>);
HttpResponse response = http.send(request);
<span style="color: #0000aa;">if</span> (response.getStatusCode() == <span style="color: #009999;">200</span>) {
System.debug(<span style="color: #aa5500;">' response.getBody() '</span> + response.getBody() );
List<<span style="color: #00aaaa;">Object</span>> employees = (List<<span style="color: #00aaaa;">Object</span>>) JSON.deserializeUntyped( response.getBody() );
<span style="color: #0000aa;">for</span>(<span style="color: #00aaaa;">Object</span> employee : employees ) {
Map<<span style="color: #00aaaa;">String</span>, <span style="color: #00aaaa;">Object</span>> currentEmployee = ( Map<<span style="color: #00aaaa;">String</span>, <span style="color: #00aaaa;">Object</span>> ) ( employee );
returnEmployees.add( currentEmployee );
}
}
system.debug( <span style="color: #aa5500;">' ExternalDataService.httpGetCallout returnEmployees '</span> + returnEmployees );
<span style="color: #0000aa;">return</span> returnEmployees;
}
<span style="color: #0000aa;">private</span> DataSource.Table getAddressTable() {
DataSource.Table table = <span style="color: #0000aa;">new</span> DataSource.Table();
List<DataSource.Column> columns;
columns = <span style="color: #0000aa;">new</span> List<DataSource.Column>();
columns.add(DataSource.Column.text(<span style="color: #aa5500;">'ExternalId'</span>, <span style="color: #009999;">255</span>));
columns.add(DataSource.Column.text(<span style="color: #aa5500;">'Street'</span>, <span style="color: #009999;">255</span>));
columns.add(DataSource.Column.text(<span style="color: #aa5500;">'ZipCode'</span>, <span style="color: #009999;">255</span>));
columns.add(DataSource.Column.url(<span style="color: #aa5500;">'DisplayUrl'</span>));
columns.add(DataSource.Column.indirectLookup(<span style="color: #aa5500;">'AddressAccountKey'</span>, <span style="color: #aa5500;">'Account'</span>, <span style="color: #aa5500;">'Account_Key__c'</span>));
<span style="color: #0000aa;">return</span> DataSource.Table.get(<span style="color: #aa5500;">'Address'</span>, <span style="color: #aa5500;">'ExternalId'</span>, columns);
}
<span style="color: #0000aa;">private</span> List<Map<<span style="color: #00aaaa;">String</span>, <span style="color: #00aaaa;">Object</span>>> getAddressData(DataSource.QueryContext context) {
List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>> dataRows = <span style="color: #0000aa;">new</span> List<Map<<span style="color: #00aaaa;">String</span>,<span style="color: #00aaaa;">Object</span>>>();
<span style="color: #0000aa;">return</span> dataRows;
}
}
</pre>
</div>
<br />
This basically :<br />
<ul>
<li>understand the context (name of the table, query criteria</li>
<li>calls the webservice </li>
<li>returns the data in <span style="background-color: white; font-family: "consolas"; font-size: 9.8pt;">List<map object="" tring="">> format. </map></span></li>
</ul>
<br />
We can see external Objects now:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/ExternalObject1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="177" data-original-width="800" height="140" src="https://springsoa.com/blogs/external/ExternalObject1.png" width="640" /></a></div>
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/ExternalObject2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="800" height="320" src="https://springsoa.com/blogs/external/ExternalObject2.png" width="640" /></a></div>
<br />
<br />
Once it is done, it is ready to be tested on Account page layout, due to indirect lookup relationship, Employee external object would be available in Account Page layout.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/ExternalDataSource2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="355" data-original-width="800" height="284" src="https://springsoa.com/blogs/external/ExternalDataSource2.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/external/ExternalDataSource3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://springsoa.com/blogs/external/ExternalDataSource3.png" width="640" /></a></div>
<br />
<br />
<br />
This way we can consume external service as external object without external Odata layer.<br />
<br />
<br />
<b>Source Code : </b><br />
<br />
<a href="https://github.com/springsoa/springsoa-salesforce/tree/master/classes">https://github.com/springsoa/springsoa-salesforce/tree/master/classes</a><br />
<a href="https://github.com/c-shah/basic-authentication">https://github.com/c-shah/basic-authentication</a><br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com1tag:blogger.com,1999:blog-1459511178626634251.post-58953178977771157032019-10-08T20:16:00.002-07:002019-10-10T09:16:09.073-07:00Lightning Component Web Service IntegrationWhen calling web service from lightning component, we can either call it from apex using @AuraEnabled method, or we can call from lightning component client side controller. This is specially important when you want to avoid governor limits on Apex side, e.g. uploading file bigger than 7MB or calling web service which could potentially take more than 120s. We can directly use browser to call the web service.<br />
<br />
Calling web service from client side controller (browser integration)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/browserIntegration/overview.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="302" data-original-width="800" height="240" src="https://springsoa.com/blogs/browserIntegration/overview.png" width="640" /></a></div>
<br />
<br />
1) We need to make sure that web service we call, allows cross origin and it is https enabled<br />
<br />
We wrote simple web service in Heroku<br />
<a href="https://basic-authentication-ws.herokuapp.com/echo">https://basic-authentication-ws.herokuapp.com/echo</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/browserIntegration/ws1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="569" data-original-width="800" height="452" src="https://springsoa.com/blogs/browserIntegration/ws1.png" width="640" /></a></div>
<br />
<br />
<br />
2) We need to make sure the web service URL is added to CSP trusted site for locker service<br />
<br />
https://basic-authentication-ws.herokuapp.com<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://springsoa.com/blogs/browserIntegration/CSP2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="344" data-original-width="800" height="274" src="https://springsoa.com/blogs/browserIntegration/CSP2.png" width="640" /></a></div>
<br />
<br />
<br />
3) Now, lightning component (client side controller)<br />
<br />
We can make call to web service<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">({
doInit: <span style="color: #0000aa;">function</span> (c, e, h) {
},
postData: <span style="color: #0000aa;">function</span> (c, e, h) {
c.set(<span style="color: #aa5500;">'v.isProcessing'</span>, <span style="color: #0000aa;">true</span>);
<span style="color: #0000aa;">var</span> xhr = <span style="color: #0000aa;">new</span> XMLHttpRequest();
<span style="color: #0000aa;">var</span> url = <span style="color: #aa5500;">'https://basic-authentication-ws.herokuapp.com/echo'</span>;
xhr.open(<span style="color: #aa5500;">'POST'</span>, url, <span style="color: #0000aa;">true</span>);
xhr.onload = <span style="color: #0000aa;">function</span> () {
<span style="color: #0000aa;">if</span> (<span style="color: #0000aa;">this</span>.status === <span style="color: #009999;">200</span>) {
c.set(<span style="color: #aa5500;">'v.isProcessing'</span>, <span style="color: #0000aa;">false</span>);
c.set(<span style="color: #aa5500;">'v.response'</span>, <span style="color: #0000aa;">this</span>.response);
}
};
xhr.setRequestHeader(<span style="color: #aa5500;">'Content-Type'</span>, <span style="color: #aa5500;">'application/json;charset=UTF-8'</span>);
xhr.send(JSON.stringify({message: c.get(<span style="color: #aa5500;">'v.message'</span>)}));
}
});
</pre>
</div>
<br />
<b><br /></b>
<b>Source Code</b><br />
<br />
<b>Heroku</b><br />
<a href="https://github.com/c-shah/basic-authentication">https://github.com/c-shah/basic-authentication</a><br />
<br />
<b>Salesforce</b><br />
<a href="https://github.com/springsoa/springsoa-salesforce/tree/master/aura/webserviceTest">https://github.com/springsoa/springsoa-salesforce/tree/master/aura/webserviceTest</a>Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-50168422786462160112017-12-11T10:50:00.001-08:002017-12-11T10:55:22.019-08:00SOSL - platform encryptionSalesforce platform encryption encrypts the data at rest, hence we can not run SOQL against it. SOSL is good work around but have its own limitation. (side note: SOSL also has limitation of <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm">2000 rows </a>returned)<br />
<br />
<a href="#1">Enable Platform Encryption</a><br />
<a href="#2">Encrypt Fields</a><br />
<a href="#3">Create Acount</a><br />
<a href="#4">SOQL</a><br />
<a href="#5">SOSL</a><br />
<br />
<br />
<div id="1">
</div>
<b>Enable Platform Encryption </b><br />
<b><br /></b>
a) create permission set<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/platformEncryption/permSet.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="800" height="337" src="https://www.springsoa.com/blogs/platformEncryption/permSet.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<b><br /></b>
b) assign system permission for this permission set<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/platformEncryption/systemPerm.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="156" data-original-width="800" height="124" src="https://www.springsoa.com/blogs/platformEncryption/systemPerm.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/platformEncryption/systemPerm2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="154" data-original-width="800" height="122" src="https://www.springsoa.com/blogs/platformEncryption/systemPerm2.png" width="640" /></a></div>
<br />
<br />
c) Assign permission set to current user<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/platformEncryption/assign.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="264" data-original-width="800" height="210" src="https://www.springsoa.com/blogs/platformEncryption/assign.png" width="640" /></a></div>
<br />
<br />
<b><br /></b>
<br />
<div id="2">
</div>
<b>Encrypt Fields</b><br />
<b><br /></b>
Fields to encrypt (Setup -> Platform Encryption)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/platformEncryption/encryptFields.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="323" data-original-width="800" height="258" src="https://www.springsoa.com/blogs/platformEncryption/encryptFields.png" width="640" /></a></div>
<br />
<div id="3">
</div>
<b><br /></b><b>Create Account</b><br />
<b><br /></b>
Created account with name : My Account<br />
<b><br /></b>
<b><br /></b>
<br />
<div id="4">
</div>
<b>SOQL</b><br />
<b><br /></b>
If we run SOQL against account, now it would return error.<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> select id, name, description from Account where name like '%My%'
</code></pre>
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> [object Object]: description from Account where name like '%My%' ^ ERROR at Row:1:Column:49 encrypted field 'Account.name' cannot be filtered in a query call
</code></pre>
<br />
<b><br /></b>
<br />
<div id="5">
</div>
<b>SOSL</b><br />
<b><br /></b>
If we run below SOSL, we can access our account:<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> FIND {My} IN ALL FIELDS RETURNING Account(Name, Description, AnnualRevenue)
FIND {M*} IN ALL FIELDS RETURNING Account(Name, Description, AnnualRevenue)
</code></pre>
<br />
<a href="https://www.springsoa.com/blogs/platformEncryption/SOSL.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="253" data-original-width="612" height="264" src="https://www.springsoa.com/blogs/platformEncryption/SOSL.png" width="640" /></a><br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
However issues comes when we want to narrow down the result. If we apply the where clause to the SOSL, it breaks. I believe it is because behind the scene SOSL still runs as SOQL.<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> FIND {M*} IN ALL FIELDS RETURNING Account(Name, Description, AnnualRevenue <b>WHERE Name LIKE 'M%'</b>)
</code></pre>
<br />
<b>Error Message</b><br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> Description, AnnualRevenue WHERE Name LIKE 'M%')
^
ERROR at Row:1:Column:82
encrypted field 'Account.Name' cannot be filtered in a query call
</code></pre>
<br />
<br />
So, we need to be careful when we write SOSL or SOQL where clause (especially in managed package) given that fields could be encrypted.<br />
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-51369836845277253942017-11-11T18:22:00.003-08:002017-11-11T18:26:07.128-08:00Salesforce Canvas : what and how?When we are integrating third party UI application, there are quite a few options<br />
<br />
<ul>
<li>Including iframe inside Visualforce or Lightning component</li>
<ul>
<li>mostly one way integration (salesforce to UI application)</li>
<li>security issues</li>
<li>sizing problems</li>
</ul>
<li>Canvas</li>
<ul>
<li>lightning and classic</li>
<li>seamless and two way integration</li>
</ul>
<li>Lightning container component (Winter'18 - for lightning)</li>
<ul>
<li>only for lightning</li>
</ul>
</ul>
<div>
Canvas by far the most feature rich and seamless integration between salesforce and third party UI application, especially we have two way interaction. E.g. salesforce passing data to third party application and UI application updating/creating data back in salesforce.</div>
<div>
<br />
<br />
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
</div>
<br />
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<div style="font-size: medium; margin: 0px;">
<span style="font-size: large;"><a href="#1">1) How to configure it</a></span></div>
<div style="font-size: medium; margin: 0px;">
<a href="#2"><span style="font-size: large;">2) </span><span style="font-size: large;">Main Features</span></a></div>
<div style="margin: 0px;">
<span id="3" style="font-size: large;"><a href="#3">3) Where Canvas Can be Used</a></span></div>
<div style="margin: 0px;">
<span style="font-size: large;"><a href="#4">4) Source Code</a></span></div>
</div>
<br />
<hr />
<br /></div>
<div>
<b><span id="1" style="font-size: large;">How to configure it</span></b></div>
<div>
<br /></div>
<div>
<ul>
<li><b>Create Connected application</b></li>
</ul>
<div>
<b><br /></b></div>
<div>
<div>
<b>Step 1) Connected App -> Allow users to install canvas personal apps</b></div>
<div>
<br /></div>
</div>
<div>
<div>
<b>Step 2) Create New App</b></div>
<div>
<br /></div>
</div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp1.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp2.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<b>Step 3) Configure Canvas App Settings (Note: check Publisher and Create Actions, if we plan to publish canvas app there)</b><br />
<div>
<br /></div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp3.png" /></a></div>
<br />
<br />
<b>Step 4)</b><br />
<br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp4.png" /></a></div>
<br />
<br />
<b>Step 5) Grab the consumer secret and key</b><br />
<br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp5.png" /></a></div>
<br />
<br />
<b>Step 6) Assign canvas app the users or profiles</b><br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp6.png" /></a></div>
<br />
<br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp7.png" /></a></div>
<br />
<br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp8.png" /></a></div>
<br />
<br />
<br />
<b>Step 7) Canvas app viewer</b><br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp9.png" /></a></div>
<br />
<br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp10.png" /></a></div>
<br />
<br />
<div>
<a href="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/CreateConnectedApp11.png" /></a></div>
<br />
<br />
<ul>
<li><b>Sample Third Party application</b></li>
<ul>
<li>From Canvas App viewer, we can create canned "heroku quick start" app, however I decided to use create my own so that I can test out all the features in controller environment</li>
<li>Create heroku application, and make sure to sign the request for the URL provided in canvas salesforce app</li>
</ul>
</ul>
<div>
<a href="https://www.springsoa.com/blogs/canvasly/herokuSignInRequest.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/herokuSignInRequest.png" /></a></div>
<br />
<br /></div>
<div>
<hr />
<div>
<b><span id="2" style="font-size: large;">Main Features</span></b></div>
<div>
<br />
<b>Security</b><br />
<ul>
<li>Salesforce sends signed request to third party application, and it can be decrypted using consumer key. Hence the request is secure.</li>
<li>Also when json request payload is decrypted, we get session id and all the information of the page context </li>
</ul>
<div>
<a href="https://www.springsoa.com/blogs/canvasly/herokuSignInRequest.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="800" src="https://www.springsoa.com/blogs/canvasly/herokuSignInRequest.png" /></a></div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/jsonDecrypted.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="624" data-original-width="714" height="558" src="https://www.springsoa.com/blogs/canvasly/jsonDecrypted.png" width="640" /></a></div>
<b><br /></b>
<b><br /></b>
<b><br /></b>
<b>Two way event</b><br />
- Events can be raised from Visual Force and can be sent to Third Party App, and Third party app can send event back to Salesforce<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/publishSubscribe.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="542" data-original-width="682" height="508" src="https://www.springsoa.com/blogs/canvasly/publishSubscribe.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/publishSubscribeUI.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="740" height="216" src="https://www.springsoa.com/blogs/canvasly/publishSubscribeUI.png" width="640" /></a></div>
<br />
<br /></div>
<div>
<b><br /></b>
<b>Resize</b><br />
<b>- </b>Third party App can use resize API to resize the canvas size in Salesforce. When we put canvas in visualforce and then in layout, we are restricted by iFrame size and height, but if canvas is put in directly in page layout we can have much better control over the size.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/resizeCode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="511" data-original-width="786" height="416" src="https://www.springsoa.com/blogs/canvasly/resizeCode.png" width="640" /></a></div>
<b><br /></b>
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/resize.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="280" data-original-width="800" height="224" src="https://www.springsoa.com/blogs/canvasly/resize.png" width="640" /></a></div>
<b><br /></b>
<b><br /></b></div>
<div>
<b><br /></b>
<b>Api calls (e.g. chatter)</b><br />
<b><br /></b>
Third party application can use OAuth token provided in JSON request and make any API call. Below is example of chatter post<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/chatterPost.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="279" data-original-width="536" height="332" src="https://www.springsoa.com/blogs/canvasly/chatterPost.png" width="640" /></a></div>
<br /></div>
<br /></div>
<div>
<hr />
<span id="3" style="font-size: large;"><b>Where canvas can be used </b></span></div>
<div>
Once it is configured correctly, it can be used at many places:<br />
<br />
<b><br /></b>
<b>Canvas App Previewer</b> : this is just for testing your canvas app<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/whereInCanvasPreviewer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="410" data-original-width="790" src="https://www.springsoa.com/blogs/canvasly/whereInCanvasPreviewer.png" /></a></div>
<br />
<b><br /></b>
<b>Page layout via Visual Force</b></div>
<div>
<div>
</div>
<div>
Canvas can be added in visual force page, however if we go that route, when we add the page to layout, we will be restricted by iframe size.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/whereInVF.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="392" data-original-width="800" height="312" src="https://www.springsoa.com/blogs/canvasly/whereInVF.png" width="640" /></a></div>
<br />
<b><br /></b>
VisualForce code:<br />
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/whereInVFCode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="423" data-original-width="576" src="https://www.springsoa.com/blogs/canvasly/whereInVFCode.png" /></a></div>
<b><br /></b>
<b><br /></b>
<b>Page Layout Directly</b></div>
<div>
</div>
This is very advantageous as we can resize and don't have to use iframe<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/whereInPureCanvas.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="407" data-original-width="800" height="324" src="https://www.springsoa.com/blogs/canvasly/whereInPureCanvas.png" width="640" /></a></div>
<br />
<br />
<b><br /></b>
<b>Lightning Component</b><br />
We can add canvas in lightning component<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/whereInLC.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="379" data-original-width="534" height="454" src="https://www.springsoa.com/blogs/canvasly/whereInLC.png" width="640" /></a></div>
<br /></div>
<div>
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<b><br /></b>
<b><br /></b>
<b>Chatter / </b><b>Publish Action</b></div>
<div>
We can also put canvas chatter or publisher action as shown below<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/canvasly/whereInChatter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="548" data-original-width="683" height="512" src="https://www.springsoa.com/blogs/canvasly/whereInChatter.png" width="640" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
<hr />
<br /></div>
<div>
<b><span id="4" style="font-size: large;">Source Code</span></b></div>
<div>
<b><br /></b>
<b>Heroku code</b> </div>
<div>
https://github.com/c-shah/canvasly<br />
<b><br /></b>
<b>Salesforce code</b></div>
<div>
https://github.com/c-shah/sf.canvasly</div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com12tag:blogger.com,1999:blog-1459511178626634251.post-15206231242941245212017-10-19T13:39:00.005-07:002017-10-19T13:39:56.787-07:00Community User Test DataAlways find a hassle to create community user for test data, so just thought putting it out here:
<br />
<br />
<b>Normal user creation:
</b><!-- HTML generated using hilite.me --><br />
<br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">User communityUser <span style="color: #333333;">=</span> new User(
ProfileId <span style="color: #333333;">=</span> [SELECT Id FROM Profile WHERE Name <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'Interconnection Community User Plus User'</span>].Id,
FirstName <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'first'</span>,
LastName <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'last'</span>,
Email <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'test.test@test.com'</span>,
Username <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'test.'</span> <span style="color: #333333;">+</span> System.currentTimeMillis() <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">'@test.com'</span>,
Title <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'Title'</span>,
Alias <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'alias'</span>,
TimeZoneSidKey <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'America/Los_Angeles'</span>,
EmailEncodingKey <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'UTF-8'</span>,
LanguageLocaleKey <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'en_US'</span>,
LocaleSidKey <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'en_US'</span>
);
</pre>
</div>
<br />
<br />
<b>But we get below errors:
</b><br />
<br />
System.DmlException: Insert failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, Cannot create a portal user without contact: [ContactId]<br />
<br />
System.DmlException: Insert failed. First exception on row 0; first error: UNKNOWN_EXCEPTION, portal account owner must have a role: []<br />
<br />
System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): Account, original object: User: []<br />
<br />
<br />
<b>To solve it : use below approach : </b><br />
<b><br /></b>
1) Create Portal Owner<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> private static User createPortalAccountOwner() {
UserRole portalRole = new UserRole(DeveloperName = 'MyCustomRole', Name = 'My Role', PortalType='None' );
insert portalRole;
System.debug('portalRole is ' + portalRole);
Profile sysAdminProfile = [Select Id from Profile where name = 'System Administrator'];
User portalAccountOwner = new User(
UserRoleId = portalRole.Id,
ProfileId = sysAdminProfile.Id,
Username = 'portalOwner' + System.currentTimeMillis() + '@test.com',
Alias = 'Alias',
Email='portal.owner@test.com',
EmailEncodingKey='UTF-8',
Firstname='Portal',
Lastname='Owner',
LanguageLocaleKey='en_US',
LocaleSidKey='en_US',
TimeZoneSidKey = 'America/Los_Angeles'
);
Database.insert(portalAccountOwner);
return portalAccountOwner;
}
</code></pre>
<br />
2) Create Community User<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> private static void createCommunityUser(User portalAccountOwner) {
System.runAs ( portalAccountOwner ) {
//Create account
Account portalAccount = new Account(
Name = 'portalAccount',
OwnerId = portalAccountOwner.Id
);
Database.insert(portalAccount);
//Create contact
Contact portalContact = new Contact(
FirstName = 'portalContactFirst',
Lastname = 'portalContactLast',
AccountId = portalAccount.Id,
Email = 'portalContact' + System.currentTimeMillis() + '@test.com'
);
Database.insert(portalContact);
User communityUser = new User(
ProfileId = [SELECT Id FROM Profile WHERE Name = 'Interconnection Community User Plus User'].Id,
FirstName = 'CommunityUserFirst',
LastName = 'CommunityUserLast',
Email = 'community.user@test.com',
Username = 'community.user.' + System.currentTimeMillis() + '@test.com',
Title = 'Title',
Alias = 'Alias',
TimeZoneSidKey = 'America/Los_Angeles',
EmailEncodingKey = 'UTF-8',
LanguageLocaleKey = 'en_US',
LocaleSidKey = 'en_US',
ContactId = portalContact.id
);
Database.insert(communityUser);
}
}
</code></pre>
<br />
3) We can use below code in testSetup or test method.<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> User portalAccountOwner = createPortalAccountOwner();
createCommunityUser(portalAccountOwner);
</code></pre>
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-61973845389630321732017-10-11T06:10:00.002-07:002017-10-11T13:39:27.789-07:00Salesforce Streaming API SummaryWas just working on streaming API and just putting thoughts together with different variations of streaming API :<br />
<br />
<br />
<ol>
<li>Push Topic</li>
<li>Generic Topic</li>
<li>Platform Event </li>
</ol>
<br />
Even though underneath the cover, they all use same technology stack, they provide very different features.<br />
<div style="-en-clipboard: true;">
<br />
<table style="-en-clipboard: true; width: 807px;"><colgroup><col style="width: 179px;"></col><col style="width: 234px;"></col><col style="width: 186px;"></col><col style="width: 208px;"></col></colgroup><tbody>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><br /></td><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">Push Topic</span></td><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">Generic Topic</span></td><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">Platform Events</span></td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">Overview</span></td><td style="border: 1px solid rgb(204, 204, 204);">Push topic is used for SOQL based subscription</td><td style="border: 1px solid rgb(204, 204, 204);">Generic Topic is used to subscribe and publish arbitrary events</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
Platform Events is used for structured publish and subscribe</div>
<div>
<br /></div>
<div>
There is a lot more native support for both publish and subscribe</div>
<div>
<br /></div>
<div>
Full control over structure (payload) of the event</div>
<div>
<br /></div>
</td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><div>
<span style="font-weight: bold;">Replay : </span></div>
<div>
<br /></div>
<div>
When client disconnect and reconnect again, they can replay from last 24 hours with id where they left from</div>
<div>
special ids : -1 from the beginning, -2 : all new events</div>
<div>
<br /></div>
</td><td style="border: 1px solid rgb(204, 204, 204);">Supported (version 36.0 +)</td><td style="border: 1px solid rgb(204, 204, 204);">Supported (version 36.0 +)</td><td style="border: 1px solid rgb(204, 204, 204);">Supported (version 36.0 +)</td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">Create (Setup)</span></td><td style="border: 1px solid rgb(204, 204, 204);"><div>
1) Using Apex Insert Statements</div>
<div>
<br /></div>
<div>
or </div>
<div>
<br /></div>
<div>
2) workbench </div>
<div>
- it defaults all the param except SOQL query</div>
<div>
<br /></div>
</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
Using Streaming Channel Tab</div>
<div>
<br /></div>
<div>
<span style="text-decoration-line: line-through;">or</span></div>
<div>
<br /></div>
<div>
<span style="text-decoration-line: line-through;">2) Workbench</span></div>
</td><td style="border: 1px solid rgb(204, 204, 204);">Create __e object</td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">Support for Trigger Subscription</span></td><td style="border: 1px solid rgb(204, 204, 204);">No</td><td style="border: 1px solid rgb(204, 204, 204);">No</td><td style="border: 1px solid rgb(204, 204, 204);">Yes</td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">How to Publish</span></td><td style="border: 1px solid rgb(204, 204, 204);">upon SOQL</td><td style="border: 1px solid rgb(204, 204, 204);">Using Rest API</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
<span style="font-family: "consolas" , "bitstream vera sans mono" , "courier new" , "courier" , monospace; font-size: 13px; line-height: 1.1em; white-space: nowrap;">EventBus.publish;</span></div>
<div>
<br /></div>
<div>
<span style="line-height: 1.45;">API :</span> <span style="line-height: 1.45;">Post like sobject</span><span style="line-height: 1.45;"> /services/data/v41.0/sobjects/Low_Ink__e/ </span></div>
<div>
<br /></div>
<div>
Process Builder</div>
<div>
<br /></div>
<div>
Flow</div>
</td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><span style="font-weight: bold;">How to Subscribe</span></td><td style="border: 1px solid rgb(204, 204, 204);"><div>
workbench (36.0) (later are causing problems)</div>
<div>
<br /></div>
<div>
using Java (cometd lib)</div>
<div>
<br /></div>
<div>
Using Javascript (cometd lib)</div>
</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
workbench</div>
<div>
<br /></div>
<div>
using Java (cometd lib)</div>
<div>
<br /></div>
<div>
Using Javascript (cometd lib)</div>
</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
using Java (cometd)</div>
<div>
<br /></div>
<div>
Using Javascript (cometd)</div>
<div>
<br /></div>
<div>
Using Visualforce (cometd)</div>
<div>
<br /></div>
<div>
Process Flow</div>
<div>
<br /></div>
<div>
Flow</div>
<div>
<br /></div>
<div>
Trigger</div>
<div>
<br /></div>
<div>
Workbench</div>
</td></tr>
<tr><td style="border: 1px solid rgb(204, 204, 204);"><div>
<span style="font-weight: bold;">Channel Name</span></div>
<div>
<br /></div>
<div>
This is useful when we subscribe using cometd library</div>
</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
/topic/<<name of="" toipc="">></name></div>
<div>
<br /></div>
<div>
e.g.</div>
<div>
/topic/<span style="font-weight: bold;">AccountUpdatePushTopic</span></div>
</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
/u/<<name of="" topic="">></name></div>
<div>
<br /></div>
<div>
e.g.</div>
<div>
/u/<span style="font-weight: bold;">GenericUpdateTopic</span></div>
</td><td style="border: 1px solid rgb(204, 204, 204);"><div>
/event/<<event api="" name="">></event></div>
<div>
<br /></div>
<div>
e.g.</div>
<div>
/event/<span style="font-weight: bold;">UpdateObjectEvent__e</span></div>
</td></tr>
</tbody></table>
</div>
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<b><span style="font-size: x-large;">Push Topic</span></b></div>
<hr />
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<span style="font-size: large;"><b>Push Topic Setup</b></span></div>
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<b><br /></b>
<b>Workbench</b></div>
<div style="-en-clipboard: true;">
we can setup a push topic using workbench, but it doesn't allow granular control over topic configuration.</div>
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/streaming/pushTopicSetup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="304" data-original-width="800" height="242" src="http://www.springsoa.com/blogs/streaming/pushTopicSetup.png" width="640" /></a></div>
<br /></div>
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<b>APEX</b><br />
<br />
<ul>
<li>NotifyForFields can be Referenced (part of query - default), Where (in where condition), All (all changes)</li>
<li>Once topic is inserted, we can use PushTopic object to make any update</li>
</ul>
</div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> PushTopic pushTopic = new PushTopic();
pushTopic.Name = 'AccountUpdatePushTopic';
pushTopic.Query = 'SELECT Id, Name, AccountNumber from Account';
pushTopic.ApiVersion = 40.0;
pushTopic.NotifyForOperationCreate = true;
pushTopic.NotifyForOperationUpdate = true;
pushTopic.NotifyForOperationUndelete = true;
pushTopic.NotifyForOperationDelete = true;
pushTopic.NotifyForFields = 'Referenced';
insert pushTopic;
</code></pre>
<div style="-en-clipboard: true;">
<br /></div>
<div style="-en-clipboard: true;">
<br />
<span style="font-size: large;"><b><br /></b></span></div>
<div style="-en-clipboard: true;">
<span style="font-size: large;"><b>Push Topic </b><b>Publish</b></span><br />
<br />
There is no API support for publish. Any change in the data based on condition (e.g. Notify Operation and fields configured) would fire the event on the topic.</div>
<div style="-en-clipboard: true;">
<span style="font-size: large;"><b><br /></b></span></div>
<div style="-en-clipboard: true;">
<span style="font-size: large;"><b><br /></b></span>
<span style="font-size: large;"><b>Push Topic </b><b>Subscribe</b></span></div>
<div style="-en-clipboard: true;">
<b><br /></b>
<b>Workbench</b><br />
<br />
<ul>
<li>This is quick way to test and also record the channel, as we will need it later for cometd library</li>
</ul>
</div>
<div style="-en-clipboard: true;">
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/streaming/pushTopicSubscribe.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="489" data-original-width="800" height="390" src="http://www.springsoa.com/blogs/streaming/pushTopicSubscribe.png" width="640" /></a></div>
<br />
<b>Java/JavaScript</b><br />
covered later</div>
<div style="-en-clipboard: true;">
<div>
<b><span style="font-size: x-large;"><br /></span></b></div>
<div>
<b><span style="font-size: x-large;"><br /></span></b></div>
<div>
<b><span style="font-size: x-large;">Generic Topic</span></b><br />
<hr />
<b><span style="font-size: x-large;"><br /></span></b></div>
<div>
<br /></div>
<div>
<div>
<span style="font-size: large;"><b>Generic Topic Setup</b></span><br />
<br />
<ul>
<li>We must setup generic toipc using salesforce UI</li>
<li>There is no apex or workbench support to create generic topic</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/streaming/genericTopicSetup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="800" height="252" src="http://www.springsoa.com/blogs/streaming/genericTopicSetup.png" width="640" /></a></div>
<span style="font-size: large;"><b><br /></b></span></div>
<div>
<span style="font-size: large;"><b><br /></b></span></div>
<div>
<span style="font-size: large;"><b>Generic Topic </b><b>Publish</b></span></div>
<div>
<span style="font-size: large;"><b><br /></b></span>
<b>Rest API (Workbench)</b><br />
<br />
<div style="-en-clipboard: true;">
<span style="color: #333333; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 13.5px; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;"><b>URL</b> : /services/data/</span><span style="border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; border-top-left-radius: 3px; border-top-right-radius: 3px; color: #333333; font-family: "dscdefaultfontbold"; font-size: 13.5px; font-weight: bold; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; word-wrap: break-word;">v<api version=""></api></span><span style="color: #333333; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 13.5px; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">/sobjects/StreamingChannel/</span><span style="background-color: white; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; border-top-left-radius: 3px; border-top-right-radius: 3px; color: #333333; font-family: "dscdefaultfontbold"; font-size: 13.5px; font-weight: bold; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; word-wrap: break-word;"><streaming channel="" id=""></streaming></span><span style="color: #333333; font-family: "monaco" , "menlo" , "consolas" , "courier new" , monospace; font-size: 13.5px; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">/push</span></div>
<div>
<br /></div>
<div>
We can find streaming channel id using query (<span style="color: #333333; font-family: monospace; font-size: 13.5px;">SELECT Name, ID FROM StreamingChannel)</span></div>
<div>
<br /></div>
<div>
<span style="font-family: "monaco";">e.g. /services/data/v40.0/sobjects/StreamingChannel/0M61I000000TN1FSAW/push</span></div>
<div>
<br /></div>
<div>
<span style="font-family: "monaco";"><b>payload : </b></span></div>
<div>
<br /></div>
<div>
<span style="font-family: "monaco";">{</span></div>
<div>
<span style="font-family: "monaco";"> "pushEvents": [</span></div>
<div>
<span style="font-family: "monaco";"> {</span></div>
<div>
<span style="font-family: "monaco";"> "payload": "Broadcast message to all subscribers",</span></div>
<div>
<span style="font-family: "monaco";"> "userIds": []</span></div>
<div>
<span style="font-family: "monaco";"> }</span></div>
<div>
<span style="font-family: "monaco";"> ]</span></div>
<br />
<div>
<span style="font-family: "monaco";">}</span></div>
<br />
<br />
<span style="font-size: large;"><b><br /></b></span></div>
<div>
<span style="font-size: large;"><b>Generic Topic </b><b>Subscribe</b></span></div>
</div>
<div>
<br /></div>
<div>
<b>WorkBench</b><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/streaming/genericTopicSubscribe.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="412" data-original-width="800" height="328" src="http://www.springsoa.com/blogs/streaming/genericTopicSubscribe.png" width="640" /></a></div>
<br />
<br />
<b>Java/JavaScript:</b><br />
Covered Later</div>
<div>
<div>
<b><span style="font-size: x-large;"><br /></span></b></div>
<div>
<b><span style="font-size: x-large;"><br /></span></b></div>
<div>
<b><span style="font-size: x-large;">Platform Events</span></b>
<br />
<hr />
</div>
</div>
<div>
<br /></div>
<div>
<div>
<span style="font-size: large;"><b>Platform Event Setup</b></span><br />
<span style="font-size: large;"><b><br /></b></span>
<span style="font-size: large;"></span><br />
<ul style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px; line-height: 1.4; margin: 0.5em 0px; padding: 0px 2.5em;">
<li style="margin: 0px 0px 0.25em; padding: 0px;">Essentially created two attributes (ObjectName__c and RecordId__c) so that we can publish the event having those attributes</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/streaming/platformEventSetup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="430" data-original-width="672" height="408" src="http://www.springsoa.com/blogs/streaming/platformEventSetup.png" width="640" /></a></div>
<span style="font-size: large;"><b><br /></b></span></div>
<div>
<span style="font-size: large;"><b><br /></b></span></div>
<div>
<span style="font-size: large;"><b>Platform Event </b><b>Publish</b></span><br />
<span style="font-size: large;"><b><br /></b></span>
<b>Apex</b><br />
<b><br /></b>
<br />
<ul style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px; line-height: 1.4; margin: 0.5em 0px; padding: 0px 2.5em;">
<li style="margin: 0px 0px 0.25em; padding: 0px;">Publish event call doesn't fail, hence we have to go through the results</li>
<li style="margin: 0px 0px 0.25em; padding: 0px;">Also publish call doesn't participate in transaction, meaning if transaction has to fail, event will still publish</li>
</ul>
<br style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px;" />
<b style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px;"></b>
<br />
<pre style="-webkit-text-stroke-width: 0px; background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; height: auto; letter-spacing: normal; line-height: 20px; orphans: 2; overflow: auto; padding: 0px; text-align: left; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; widows: 2; width: 1009.8px; word-spacing: 0px;"><code style="color: black; word-wrap: normal;"> List<updateobjectevent__e> events = new List<updateobjectevent__e>();
UpdateObjectEvent__e event = new UpdateObjectEvent__e(objectName__c='Account', recordId__c='1234');
events.add( event );
List<database .saveresult=""> results = EventBus.publish(events);
if( results != null && results.size() > 0 ) {
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
System.debug('Successfully published event. ' + results.size() );
} else {
for(Database.Error err : sr.getErrors()) {
System.debug('Error returned: ' + err.getStatusCode() + ' - ' + err.getMessage());
}
}
}
} else {
System.debug(' Noting is published. ');
}
</database></updateobjectevent__e></updateobjectevent__e></code></pre>
<b><br /></b>
<b><br /></b>
<b>Rest API</b><br />
<b><br /></b>
<span style="background-color: white; color: #222222; font-family: "arial" , "tahoma" , "helvetica" , "freesans" , sans-serif; font-size: 13.2px;">Endpoint : /services/data/v40.0/sobjects/UpdateObjectEvent__e/</span><br />
<span style="background-color: white; color: #222222; font-family: "arial" , "tahoma" , "helvetica" , "freesans" , sans-serif; font-size: 13.2px;">Payload : { "RecordId__c" : "123455634343", "ObjectName__c" : "Account" }</span><br />
<div style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px;">
<br /></div>
<div class="separator" style="background-color: white; clear: both; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px; text-align: center;">
<a href="http://www.springsoa.com/blogs/PlatformEvents/RestAPIPublish.png" imageanchor="1" style="color: #7c93a1; margin-left: 1em; margin-right: 1em; text-decoration-line: none;"><img border="0" data-original-height="342" data-original-width="778" height="280" src="http://www.springsoa.com/blogs/PlatformEvents/RestAPIPublish.png" style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border: 1px solid rgb(238, 238, 238); box-shadow: rgba(0, 0, 0, 0.1) 1px 1px 5px; padding: 5px; position: relative;" width="640" /></a></div>
<div class="separator" style="background-color: white; clear: both; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px; text-align: center;">
<br /></div>
<div class="separator" style="background-color: white; clear: both; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px; text-align: center;">
<br /></div>
<b>Soap API</b><br />
Similar to rest api, it is just call to insert into sObject<br />
<br />
<b>Process Builder </b><br />
not covered, but straightforward<br />
<b><br /></b>
<b>Visual Flow </b><br />
not covered, but straightforward<br />
<b><br /></b>
</div>
<div>
<span style="font-size: large;"><b><br /></b></span>
<span style="font-size: large;"><b>Platform Event </b><b>Subscribe</b></span></div>
</div>
<div>
<span style="font-size: large;"><b><br /></b></span>
<b>Trigger</b><br />
<br />
<ul>
<li>only after insert is supported</li>
</ul>
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> trigger UpdateObjectEventTrigger on UpdateObjectEvent__e (after insert) {
System.debug(' UpdateObjectEventTrigger ' );
for (UpdateObjectEvent__e event : Trigger.New) {
System.debug(' : ' + event.RecordId__c + ' ' + event.ObjectName__c );
}
}
</code></pre>
<span style="font-size: large;"><br /></span><b>Process Builder </b><br />
not covered, but straightforward<br />
<b><br /></b>
<b>Visual Flow </b><br />
not covered, but straightforward<br />
<div>
<br /></div>
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<div style="margin: 0px;">
<b>Java/JavaScript </b></div>
<div style="margin: 0px;">
Covered later</div>
</div>
</div>
<div>
<br />
<br />
<span style="font-size: large;"><b>Platform Event </b><b>Debug</b></span><br />
<br />
<ul>
<li>debug statements in trigger doesn't show up in debug logs, we need to enable them using below</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/streaming/platformEventDebug.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="800" height="164" src="http://www.springsoa.com/blogs/streaming/platformEventDebug.png" width="640" /></a></div>
<div>
<br /></div>
<br />
<span style="font-size: large;"><b><br /></b></span>
<span style="font-size: large;"><b><br /></b></span>
<span style="font-size: large;"><b><br /></b></span>
<span style="font-size: large;"><b><br /></b></span>
<br />
<div>
<b><span style="font-size: x-large;">Generic Java Subscriber Client</span></b><br />
<hr />
</div>
<span style="font-size: large;"></span><br />
<div style="font-size: medium;">
<div>
</div>
</div>
<br />
<div style="color: black; font-family: "times new roman"; font-size: medium; font-style: normal; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">
<div style="font-weight: normal; margin: 0px;">
We need to download and build the EMP connector from salesforce </div>
<ul style="font-weight: normal;">
<li>download : https://github.com/forcedotcom/EMP-Connector</li>
<li>unzip, and run "mvn clean install"</li>
<li>Use emp-connector-0.0.1-SNAPSHOT-phat.jar in the new project that we are going to create</li>
<li>Create a new project (java 1.8) and use below code. </li>
<li>Please note that channel name can be changed as per which topic we are subscribing</li>
</ul>
<div style="font-weight: normal;">
<br /></div>
<div style="font-weight: normal;">
<br /></div>
<div style="font-weight: normal; margin: 0px;">
<br /></div>
<pre style="background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> package com.spring.client;
import com.salesforce.emp.connector.BayeuxParameters;
import com.salesforce.emp.connector.EmpConnector;
import com.salesforce.emp.connector.TopicSubscription;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static com.salesforce.emp.connector.LoginHelper.login;
public class StreamingClient {
public static void main(String args[]) throws Exception {
long replayFrom = EmpConnector.REPLAY_FROM_EARLIEST;
BayeuxParameters params = login("streaming@springsoa.com", "Welcome1");
EmpConnector connector = new EmpConnector(params);
Consumer<Map<String, Object>> consumer = event -> System.out.println(String.format("Received:\n%s", event));
connector.start().get(5, TimeUnit.SECONDS);
TopicSubscription subscription = connector.subscribe("<b>/event/UpdateObjectEvent__e</b>", replayFrom, consumer ).get(5, TimeUnit.SECONDS);
System.out.println(String.format("Subscribed: %s", subscription));
//subscription.cancel();
//connector.stop();
}
}
</code></pre>
<div style="font-weight: normal; margin: 0px;">
<br />
<br /></div>
<div style="font-weight: normal;">
<b><span style="font-size: x-large;"><br /></span></b>
<b><span style="font-size: x-large;">Generic Javascript Subscriber Client</span></b><br />
<hr />
</div>
<div style="font-weight: normal;">
<br /></div>
<div style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px;">
We can use cometD library to listen to the event, I had to write customer wrapper (cometdCustom.js) to greatly simply the visualforce page. We can also use this in independent HTML page,as long as we can get oauth session id.</div>
<div style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px;">
<b><br /></b></div>
<div class="separator" style="background-color: white; clear: both; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px; text-align: center;">
<a href="http://www.springsoa.com/blogs/PlatformEvents/refreshVF.png" imageanchor="1" style="color: #5dc2c0; margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="316" data-original-width="800" height="252" src="http://www.springsoa.com/blogs/PlatformEvents/refreshVF.png" style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border: 1px solid rgb(238, 238, 238); box-shadow: rgba(0, 0, 0, 0.1) 1px 1px 5px; padding: 5px; position: relative;" width="640" /></a></div>
<div style="font-weight: normal;">
</div>
<div style="font-weight: normal;">
<br /></div>
<div style="color: black; font-family: "times new roman"; font-size: medium; font-style: normal; font-weight: normal; letter-spacing: normal; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">
<div style="margin: 0px;">
<br /></div>
</div>
</div>
<br />
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: "times new roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
</div>
<br />
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: "times new roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; letter-spacing: normal; margin: 0px; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<div style="font-weight: normal; margin: 0px;">
<br /></div>
<div>
<b><span style="font-size: x-large;">Source Code</span></b><br />
<hr />
</div>
<div style="font-weight: normal; margin: 0px;">
Java code : <a href="https://github.com/c-shah/streaming-java-client" target="_blank">https://github.com/c-shah/streaming-java-client</a> </div>
<div style="font-weight: normal; margin: 0px;">
Salesforce and JS code : <a href="https://github.com/c-shah/salesforce-streaming" target="_blank">https://github.com/c-shah/salesforce-streaming</a></div>
</div>
</div>
</div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com2tag:blogger.com,1999:blog-1459511178626634251.post-10386187095533402132017-10-11T03:56:00.002-07:002017-10-11T13:34:52.744-07:00Refresh VisualForce Page with Platform Events<b><span style="font-size: large;">Problem Statement </span></b><br />
<br />
A visual force page is embedded inside the standard page layout to display additional information from third party application as below.<br />
<br />
<b>A few use cases :</b><br />
- Let's say there is change inside third party application content and we want to refresh the entire page<br />
- If third party application is making change to sales force data on this page using API and we need to refresh the page<br />
<br />
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">A few failed solutions</span></b><br />
<br />
1) If third party application is rendered inside the iframe, we can not access the parent salesforce page.<br />
<br />
E.g. if we try one of the below we get the error message, as salesforce will prevent third party iframe to access the parent page.<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> window.parent.location.href = URL
parent.location.href=parent.location.href
parent.location.reload();
window.parent.location.href = window.parent.location.href
</code></pre>
<br />
error message:<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> Unsafe JavaScript attempt to initiate navigation for frame with URL 'https://c.na59.visual.force.com/servlet/servlet.Integration?lid=066f4000001yE8F&ic=1&...'.
The frame attempting navigation is neither same-origin with the target, nor is it the target's parent or opener
</code></pre>
<br />
<br />
2) Polling : in parent visualforce page we can constantly do polling on server side to listen for changes and refresh the VF page as needed. Polling in general is not good idea and not very scalable.<br />
<br />
<br />
<br />
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">Platform Event to Rescue</span></b><br />
<br />
Event based solution comes quite handy here, where we can publish the event on server side and visualforce page can listen to event and on right criteria, it can alert the end user on change or refresh the page so that we can see the fresh data.<br />
<br />
Platform event is glorified version of streaming api, and we can get more details on a separate post <a href="http://chintanblog.blogspot.com/2017/10/salesforce-streaming-api-summary.html" target="_blank">here</a>.<br />
<br />
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">Solution:</span></b><br />
<br />
1. Create platform event<br />
2. Upon change, publish event using rest api or on Apex<br />
3. On visualforce page, listen to those event and refresh the page<br />
<br />
<br />
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">Create platform event </span></b><br />
<br />
<ul>
<li>Essentially created two attributes (ObjectName__c and RecordId__c) so that we can publish the event having those attributes</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/PlatformEvents/PlatformEvent.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/PlatformEvents/PlatformEvent.png" data-original-height="517" data-original-width="800" height="412" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">Publish event using rest api or on Apex</span></b><br />
<div>
<b><br /></b>
<b><br /></b>
<b>1) Publish via Apex</b></div>
<ul>
<li>Publish event call doesn't fail, hence we have to go through the results</li>
<li>Also publish call doesn't participate in transaction, meaning if transaction has to fail, event will still publish</li>
</ul>
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> List<UpdateObjectEvent__e> events = new List<UpdateObjectEvent__e>();
UpdateObjectEvent__e event = new UpdateObjectEvent__e(objectName__c='Account', recordId__c='1234');
events.add( event );
List<Database.SaveResult> results = EventBus.publish(events);
if( results != null && results.size() > 0 ) {
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
System.debug('Successfully published event. ' + results.size() );
} else {
for(Database.Error err : sr.getErrors()) {
System.debug('Error returned: ' + err.getStatusCode() + ' - ' + err.getMessage());
}
}
}
} else {
System.debug(' Noting is published. ');
}
</code></pre>
<b><br /></b>
<b>2) Publish via Rest API</b><br />
<br />
Endpoint : /services/data/v40.0/sobjects/UpdateObjectEvent__e/<br />
Payload : { "RecordId__c" : "123455634343", "ObjectName__c" : "Account" }<br />
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/PlatformEvents/RestAPIPublish.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/PlatformEvents/RestAPIPublish.png" data-original-height="342" data-original-width="778" height="280" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">Listen to event on Visualforce Page </span></b><br />
<div>
<b><br /></b></div>
<div>
We can use cometD library to listen to the event, I had to write customer wrapper (cometdCustom.js) to greatly simply the visualforce page. </div>
<div>
<b><br /></b></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/PlatformEvents/refreshVF.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/PlatformEvents/refreshVF.png" data-original-height="316" data-original-width="800" height="252" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
<br /></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<span style="font-size: large;"><b>
Source code </b></span><br />
It can be found at : <a href="https://github.com/c-shah/salesforce-streaming" target="_blank">https://github.com/c-shah/salesforce-streaming</a><br />
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-34858245499972786512017-09-18T21:59:00.001-07:002017-10-03T19:00:19.376-07:00Environment Hub - One stop shopIt is always a nightmare to keep working with username / password / tokens with different dev orgs or sandboxes.<br />
<br />
- Keeping eye on different username / passwords for dev orgs and sandboxes<br />
- Giving Access to your sandbox / dev org to others<br />
- White listing the IPs to avoid tokens being asked<br />
- Sandbox Refresh - changing email and verification<br />
- Scripts to reset passwords or profiles or emails, etc.<br />
<br />
I believe Environment Hub is quite sleek solution.<br />
<br />
<h4>
Install Environment Hub Application</h4>
<br />
<ul>
<li>You will need to contact customer support to have that App installed</li>
<li>In case of production org (non ISV), it should be installed in production org</li>
<li>for ISV, it is more flexible, but prefer to be at same place as LMA org</li>
</ul>
<br />
<br />
<h4>
Configuration</h4>
<br />
<ul>
<li>Select Environment Hub App</li>
<li>Add Environment Hub tab</li>
<li>In case of production org, all sandboxes should be auto discovered </li>
<li>In case of ISV, we might want to register different Developer org to Environment hub</li>
<li>We should give all users who need to use Environment Hub, appropriate access to their profile</li>
<ul>
<li>Manage Environment Hub</li>
<li>Connect Organization to Environment Hub</li>
</ul>
</ul>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/environmentHub/Profile.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/environmentHub/Profile.png" data-original-height="304" data-original-width="686" height="282" width="640" /></a></div>
<br />
<h4>
Sandboxes</h4>
<br />
<ul>
<li>Sandboxes are auto discovered by Environment Hub</li>
<li>We should enable the SSO on it</li>
<li>Once SSO is enabled, it is <b>required</b> to refresh this sandbox</li>
<li>Once that is done, any production user (with Connect Organization permission) will be able to login to that org!</li>
<li>No more email reset, password rest, white listing IP, ...</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/environmentHub/sandboxOrg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/environmentHub/sandboxOrg.png" data-original-height="545" data-original-width="755" height="460" width="640" /></a></div>
<div>
<br /></div>
<br />
<br />
<h4>
Development Orgs</h4>
<br />
<ul>
<li>We can connect any dev org to Environment Hub</li>
<li>We should enable SSO on it</li>
<li>Now there are 3 different method to map Environment Hub user to Dev org</li>
<ul>
<li>User name mapping - we can map the user name from Env hub to Dev org - manually</li>
<li>Federation Id : in case of SSO, as long as federation id matches between dev org and env hub org</li>
<li>User name formula field - apply the user name formula field so that env hub user can be converted to one of the Dev org user</li>
</ul>
</ul>
<div>
In most cases, there is only one user that we care about, hence I use third approach (formula field) to give all users in Environment hub access to dev org.</div>
<div>
<br /></div>
<div>
E.g. if dev org user is dev2@ot.com, I will make formula field to be "dev2@ot.com", hence all environment hub user will evaluate to "dev2@ot.com" and would have full access to my dev org.</div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/environmentHub/DevOrg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/environmentHub/DevOrg.png" data-original-height="400" data-original-width="800" height="320" width="640" /></a></div>
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-35748838944666506632017-09-18T20:26:00.001-07:002017-09-18T20:33:13.799-07:00Salesforce Big ObjectsWas just experimenting with Salesforce <a href="https://resources.docs.salesforce.com/sfdc/pdf/big_objects_guide.pdf" target="_blank">Big Objects</a> and found it quite interesting. It is mainly used for big data (100M+) analytics and mostly for asynchronous data crunching like Hadoop. However, there are very critical distinction before we go with Big Object.<br />
<br />
<br />
<ul>
<li>Currently Big Object only support fields and permission, and that's about it</li>
<li>We can <b>not </b>have</li>
<ul>
<li>triggers</li>
<li>page layouts</li>
<li>extensive SOQL (indexed SOQL is supported but that's extremely limited - and make sense as we are dealing with humongous data set)</li>
<li>no workflows, process builders, etc..</li>
<li>no reports</li>
</ul>
<li>Basically it is completely non UI, and just for back end data stores for big data analytics - and that's about it.</li>
</ul>
<div>
<br /></div>
<h3>
Use case</h3>
<div>
In org, we can have survey on and Object record (e.g. Account, Opportunity, etc..), and would want to store those survey data in Big Object and later analyze it. </div>
<h3>
</h3>
<div>
<br /></div>
<h3>
How to user it :</h3>
<h4>
</h4>
<div>
<br /></div>
<h4>
1. Create Big Object</h4>
<div>
<ul>
<li>There is no User Interface to create the Big Object and its fields. We must use metadata API (using Ant Migration tool or workbench) to create such artifacts. Obviously, workbench makes it a lot easier.</li>
<li>Create object <a href="http://www.springsoa.com/blogs/bigObjects/Survey__b.object" target="_blank">file </a></li>
<li>Create permission set <a href="http://www.springsoa.com/blogs/bigObjects/Survey_BigObject.permissionset" target="_blank">file</a></li>
<li>Create package.xml <a href="http://www.springsoa.com/blogs/bigObjects/package.xml" target="_blank">file</a></li>
<li>Nicely bundle them in .zip file and in right directory structure (you can download it from <a href="http://www.springsoa.com/blogs/bigObjects/SurveyBigObject.zip" target="_blank">here</a>)</li>
<li>Use workbench to deploy the .zip file and BigObject should like below</li>
<li>You can assign permission set (Survey_BigObject) to right user so they can query and update the data</li>
</ul>
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/bigObjects/SurveyBigObject.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/bigObjects/SurveyBigObject.png" data-original-height="356" data-original-width="800" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br />
<ul>
<li>Pay close attention to indexed fields, this is used for inserting records - identifying duplicates, and issuing synchronous SOQL queries. </li>
</ul>
</div>
<div>
<br /></div>
<h4>
2. Insert Data</h4>
<div>
<ul>
<li>We can insert data just like we do in Apex for any other Object, or we can use Bulk API</li>
<li>There is no upsert operation, salesforce will automatically check the record being inserted against the indexed value, and if index values are same, then it will do update. otherwise insert.</li>
<li>Upon failure, there are no errors, we just need to look at saveResult or saveResults.</li>
</ul>
<div>
<br /></div>
</div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> Account a = [ select id, name from account limit 1 ];
Survey__b survey = new Survey__b();
survey.WhatID__c = a.id;
survey.WhatTime__c = System.today() + 1;
survey.WhatObject__c = 'Account';
survey.Question1__c = 'What is the rating';
survey.Answer1__c = '1';
<b> Database.SaveResult saveResult = database.insertImmediate(survey);
</b> System.debug( ' success ' + saveResult.isSuccess() + ' ' + saveResult );
</code></pre>
<div>
<br />
<br />
<br /></div>
<h4>
3. Query Data</h4>
<div>
<ul>
<li>Querying data is quite tricky with Big Object. Either you can query all records, which most probably is going to fail when we have millions of records</li>
<li>Or synchronous SOQL can only be issued against indexed fields. And also indexed fields must be in order in the query. See below for example:</li>
</ul>
<div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> List<Survey__b> surveys = [ select id, WhatId__c, WhatObject__c, WhatTime__c, Question1__c, Answer1__c from Survey__b ];
for(Survey__b survey : surveys ) {
System.debug( survey );
}
System.debug(' -------------- indexed query -------------- ');
/** no gap is allowed and only indexed field in exact order can be used for query , we can skip but no gap is allowed, e.g. below
* [select id from Survey__b] is fine
* [select id from Survey__b where WhatID__c = :a.id ] is fine
* [select id from Survey__b where WhatTime__c = :System.today() ] is NOT fine, as you can't jump to index2 without having index1 in the query.
* **/
Account a = [ select id, name from account limit 1 ];
List<Survey__b> surveys2 = [ select id, WhatId__c, WhatObject__c, WhatTime__c, Question1__c, Answer1__c from Survey__b where WhatID__c = :a.id and WhatTime__c = :System.today() ];
for(Survey__b survey : surveys2 ) {
System.debug( survey );
}
</code></pre>
<br /></div>
</div>
<h4>
4. Asynchronous SOQL</h4>
<div>
<br />
<ul>
<li>Asynchronous soql is only supported using Rest API</li>
<li>We have to provide asynchronous SOQL and then the custom Object to store the result</li>
<li>It seems like only one Async SOQL can run at any given time - at least in org I worked on</li>
</ul>
</div>
<h4>
4.1 Create Custom Object To Store Async Result</h4>
<div>
<br />
<ul>
<li>Created suvey analysis object to store the analysis of the query with counts</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/bigObjects/SurveyAnalysisObject.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/bigObjects/SurveyAnalysisObject.png" data-original-height="613" data-original-width="703" height="558" width="640" /></a></div>
<div>
<br /></div>
</div>
<h4>
4.2 Run Asynchronous SOQL</h4>
<div>
<ul>
<li>Below is how asynchronous SOQL looks like, we need to provide SOQL, and then target table and mapping between selected field and target table's field.</li>
</ul>
<div>
<br /></div>
</div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> {
"query": "select Question1__c, Answer1__c, count(whatId__c) c from Survey__B where WhatObject__c = 'Account' group by Question1__c, Answer1__c",
"operation": "insert",
"targetObject": "Survey_Analysis__c",
"targetFieldMap": {
"Question1__c": "Question1__c",
"Answer1__c": "Answer1__c",
"c":"Count__c"
}
}
</code></pre>
<div>
<br /></div>
<div>
<br />
<br />
<ul>
<li>We can execute it using Rest API on Workbench</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/bigObjects/AsyncSOQL.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/bigObjects/AsyncSOQL.png" data-original-height="420" data-original-width="800" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Once Asynchonous SOQL query job is completed, we can query <span style="background-color: #f0f0f0; font-family: "arial"; font-size: 12px;">Survey_Analysis__c </span> object for accumulated result.</div>
<div>
<br /></div>
<div>
<br /></div>
</div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com17tag:blogger.com,1999:blog-1459511178626634251.post-34488439949165923252017-09-17T03:57:00.004-07:002017-10-03T19:01:13.678-07:00Salesforce Platform CacheNothing new, just short summary on platform cache :<br />
<br />
To over simplify, Platform cache is glorified hash map. Platform cache is first divided into different partitions. These are hard partition, cache usage in one partition will not overflow in another partition. Usually different partition is used for different project.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://springsoa.com/blogs/platformCache/partition.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://springsoa.com/blogs/platformCache/partition.png" data-original-height="390" data-original-width="800" height="312" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Partitions are further divided into Org cache (available to all users) and Session cache (used for user session cache). These are again hard partition, cache from Org will not overflow to Session. Minimum cache size is 5MB for Org or Session Cache.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://springsoa.com/blogs/platformCache/OrgSession.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://springsoa.com/blogs/platformCache/OrgSession.png" data-original-height="393" data-original-width="800" height="313" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h3 style="clear: both; text-align: left;">
<b><span style="font-size: medium;">To store key in the Cache</span></b></h3>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: User u = [ select id, username from user where username = 'chintan_shah@abc.com' ];
2: Cache.Org.put( 'local.MyPartition1.key1', u );
3: System.debug(' key1 is stored ');
4:
5: String name = 'Chintan';
6: Cache.OrgPartition MyPartition1 = Cache.Org.getPartition('local.MyPartition1');
7: MyPartition1.put('key2', name );
8: System.debug(' key2 is stored ');
</code></pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>We can either put key directly using Cache.Org.put or get access to a specific partition using Cache.Org.getPartition</li>
<li>To work with Session partition, Org would just change to Session in above code.</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h3 style="clear: both; text-align: left;">
<b><span style="font-size: medium;">To retrieve the key from Cache</span></b></h3>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: Object u = Cache.Org.get( 'local.MyPartition1.key1');
2: System.debug(' key1 is stored ' + u );
3:
4: Cache.OrgPartition MyPartition1 = Cache.Org.getPartition('local.MyPartition1');
5: System.debug(' key2 is stored ' + MyPartition1.get('key2' ) );
</code></pre>
<b><br /></b>
<b><br /></b>
<b><br /></b>
<br />
<h3>
<b><span style="font-size: medium;">To clean the Cache</span></b></h3>
<br />
<ul>
<li>Cache is not guaranteed persistence store, it can be cleaned any time by Salesforce </li>
<li>Can also be wiped out during code deployment</li>
<li>Session cache max TTL is 8 hours and Org is 24 hours</li>
<li>Internally it uses LRU when it hits size limit to clean up old data</li>
</ul>
<div>
Hence, we don't ever have to do the clean up, but if we need to for certain reason, we can use below code. It also has limitation if cache is stored using cache builder.</div>
<div>
<br /></div>
<div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: for(String key : Cache.Org.getKeys() ) {
2: Cache.Org.remove(key);
3: }
4:
5: for(String key : Cache.Session.getKeys() ) {
6: Cache.Session.remove(key);
7: }
</code></pre>
<br /></div>
<br />
<br />
<h3>
<b><span style="font-size: medium;">Cache Builder</span></b></h3>
<br />
Instead of storing and retrieving cache, it is better to provide loading strategy to Platform cache, so upon cache miss, Salesforce automatically calls the class to load the cache for that key. This reduces the code and handles cache miss much more gracefully.<br />
<br />
We have to specify cache loading strategy as class. Below is small class which loads the user information based on username. Idea is username is being the key, we need to load user data if not already in the cache.<br />
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: /**
2: * Created by chshah on 9/14/2017.
3: */
4:
5: public class UserInfoCache implements Cache.CacheBuilder {
6:
7: public Object doLoad(String usernameKey) {
8: String username = keyToUserName(usernameKey);
9: System.debug(' UserInfoCache load usernameKey ' + usernameKey + ' userName ' + username );
10: User u = (User)[SELECT Id, firstName, LastName, IsActive, username FROM User WHERE username =: username];
11: return u;
12: }
13:
14: public static String usernameToKey(String username) {
15: return username.replace('.','DOT').replace('@','ATRATE');
16: }
17:
18: public static String keyToUserName(String key) {
19: return key.replace('DOT','.').replace('ATRATE','@');
20: }
21: }
</code></pre>
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: String usernameKey = UserInfoCache.usernameToKey('chintan_shah@abc');
2: User u = (User) Cache.Org.get(UserInfoCache.class, 'local.MyPartition1.' + usernameKey );
3: System.debug( ' u ' + u );
</code></pre>
<br />
<br />
<ul>
<li>The reason for converting username to usernameKey, is special characters are not allowed in platform cache key. </li>
<li>In line 2, we provide cache loading strategy, hence if key is not found, it will call our class to load the key.</li>
</ul>
<br />
<br />
<h3>
<b>Consideration</b></h3>
<br />
<ul>
<li>ISV (managed package) can supply their own cache, hence it will use different namespace than "local"</li>
<li>Cache put follows same transaction boundary as SOQL updates, so any rollback due to failure will not put data in cache</li>
<li>Cache TTL limits (8/24 hours), and plus limit on how much data we can store per transaction (usually 1MB)</li>
</ul>
<br />
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-52633099886875959862017-09-16T19:41:00.001-07:002017-09-16T19:43:03.611-07:00Bad @FutureWhen writing apex methods, it is very tempting to go for @future if we want some work to be done asynchronously in separate transaction. @future is definitely easy to use but comes with a lot of limitation.<br />
<br />
<br />
Let's say I want expose an API to all developers and want to do some work which is quite resource intensive, hence I breakdown my code and put it in @future which is going to take a bit long time and might need higher governor limit.
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: /**
2: * Created by chshah on 9/15/2017.
3: */
4: public with sharing class MyCoolApex {
5: /**
6: * I plan to provide this method to rest of the developers to consume.
7: */
8: public static void myExposedMethod() {
9: // do task 1
10: // do task 2
11: // do task 3
12: // do future task
13: System.debug(LoggingLevel.INFO, 'Inside myExposedMethod - Calling future task for additional work.');
14: <b>doFutureTask()</b>;
15: }
16: <b>@future</b>
17: private static void doFutureTask() {
18: System.debug(LoggingLevel.INFO, 'Doing Future Resoruce Intensive Task');
19: }
20: }
</code></pre>
<br />
Here I am exposing MyCoolApex.myExposedMethod for other developers to consume, and calling doFutureTask to delay some resource intensive processing.<br />
<br />
It works fine most of the time (until it doesn't), e.g. if someone calling :<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> MyCoolApex.myExposedMethod();
</code></pre>
<br />
However the problem comes if someone is trying to call this method from Batch, Schedule or even Future. If our method is called from Batch process, Scheduled process or Future (which makes nested Future), salesforce will halt the processing with error. e.g. Client code : <br />
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> /**
* Created by chshah on 9/16/2017.
*/
public with sharing class TestInBatch implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext BC) {
return Database.getQueryLocator('select id from user limit 1');
}
public void execute(Database.BatchableContext BC, List<sObject> accounts) {
MyCoolApex.myExposedMethod();
}
public void finish(Database.BatchableContext BC) {
}
}
</code></pre>
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> TestInBatch tb = new TestInBatch();
Database.executeBatch(tb);
</code></pre>
<br />
<br />
This would result in error. In order to solve it, we could put some catches inside our API, e.g. below, but that doesn't really solve the problem.<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: public static void myExposedMethod() {
2: // do task 1
3: // do task 2
4: // do task 3
5: // do future task
6: <b>if( System.isBatch() || System.isFuture() || System.isScheduled() ) { </b>
7: System.debug(LoggingLevel.INFO, 'Inside myExposedMethod - Unable to call future method.');
8: } else {
9: System.debug(LoggingLevel.INFO, 'Inside myExposedMethod - Calling future task for additional work.');
10: doFutureTask();
11: }
12: }
</code></pre>
<br />
<br />
<b>Correct Solution (Queueable) </b><br />
<br />
The right way to solve the problem would be to use Queueable. Queueable has least amount of restrictions, you can call Queueable from Future, Batch, Schedulable, and even Queueable. In Dev org, we can nest Queueable upto 5 times and in Enterprise Org, there is no limit on nested Queueable.<br />
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: /**
2: * Created by chshah on 9/15/2017.
3: */
4: public with sharing class MyCoolApex {
5: /**
6: * I plan to provide this method to rest of the developers to consume.
7: */
8: public static void myExposedMethod() {
9: // do task 1
10: // do task 2
11: // do task 3
12: // do future task
13: System.debug(LoggingLevel.INFO, 'Calling Queuable');
14: <b>System.enqueueJob( new MyCoolApexQueuable() );</b>
15: }
16: }
</code></pre>
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: /**
2: * Created by chshah on 9/16/2017.
3: */
4: public with sharing class MyCoolApexQueuable implements Queueable, Database.AllowsCallouts {
5: public void execute(QueueableContext context) {
6: <b>doFutureTask();</b>
7: }
8: private static void <b>doFutureTask()</b> {
9: System.debug(LoggingLevel.INFO, 'Doing Resoruce Intevensive Task');
10: }
11: }
</code></pre>
<br />
<br />
In above example, the actual API method (<span style="background-color: #f0f0f0; font-family: "arial"; font-size: 12px;">myExposedMethod), </span> just calls Queue to defer the resource intensive work in asynchronous fashion. Now, we don't have to worry who (or which context) our method is called.<br />
<br />
<br />
<br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-14301781368059061482017-09-14T08:50:00.004-07:002017-09-16T13:22:41.907-07:00Ten Commandments for Salesforce Test1. Thou shalt not use "Test.isRunning" in actual code<br />
<ul>
<li>Use Mock e.g. Test.setMock(HttpCalloutMock.class, new MyMockHttpService())</li>
<li>There might be an extream case, but it is always best practice to keep actual code clean without Test.isRunning</li>
</ul>
<br />
<br />
2. Thou shalt use Test.start and stop<br />
<ul>
<li>It resets governer limit!</li>
<li>In some cases it is necessary, e.g. if you require to do some DML to setup data and actual test code has callout</li>
<li>All asynchronous code gets executed right away - and we never know which code will have asynchronous code now or in future</li>
</ul>
<br />
<br />
3. Thou shalt use Test runAs whenever possible<br />
It makes sure code is running fine as expected profile.<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
User u = new User(Alias = 'standt', Email='standarduser@testorg.com', EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US', LocaleSidKey='en_US', ProfileId = p.Id, TimeZoneSidKey='America/Los_Angeles', UserName='standarduser@testorg.com');
System.runAs(u) {
System.debug('Current User: ' + UserInfo.getUserName());
System.debug('Current Profile: ' + UserInfo.getProfileId());
}
</code></pre>
<span style="white-space: pre;"> </span><br />
<br />
4. Thou shalt use System.assert - after Test.stopTest, and should have meaningful message<br />
<br />
<ul>
<li>Must use System.assert</li>
<li>It should always be after Test.stopTest, as asynchronous code gets executed on Test.stopTest line</li>
<li>Have a meaningful error message : System.assertEquals( A, B, "Message");</li>
</ul>
<br />
<br />
5. Thou shalt use @testSetup<br />
<ul>
<li>Only one method can have @testSetup</li>
<li>It is called in separate transaction, so don't try to set variable, just use for data prepeartion</li>
</ul>
<br />
<br />
6. Thou shalt use private for class<br />
<ul>
<li>class should be marked @isTest and private </li>
</ul>
<div>
<br />
<br />
7. Thou shalt use private for method</div>
<div>
<ul>
<li>Method should be marked @isTest and private</li>
</ul>
</div>
<br />
<br />
8. Thou shalt use Test Data Factory<br />
<ul>
<li>Instead of creating test data in class, it should be seperate routine - as chances are same data would be created again.</li>
<li>We can use Test.loadData(Account.sObjectType, 'myResource') or create based on parameters, but it is good to externalize</li>
</ul>
<br />
<br />
9. Thou shalt never use @seeAllData<br />
<ul>
<li>obviously!</li>
</ul>
<div>
<br />
<br />
10. Test Behavior over Coverage</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Based on above, here is my template I use :</div>
<div>
<br /></div>
<div>
<b>My Sample Class:</b><br />
<br /></div>
<div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> /**
* Created by chshah on 9/14/2017.
*/
public with sharing class My {
public static List<Contact> changeContactName(List<Contact> contacts) {
for(Contact c : contacts ) {
c.firstName = c.firstName.toUpperCase();
}
update contacts;
return contacts;
}
}
</code></pre>
<br /></div>
<div>
<br /></div>
<div>
<b>Test Class:</b></div>
<div>
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> /**
* Created by chshah on 9/14/2017.
*/
@isTest
private class MyTest {
@testSetup
private static void testSetup() {
}
@isTest
private static void testChangeContactName() {
Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
User u = new User(Alias = 'standt', Email='standarduser@testorg.com', EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US', LocaleSidKey='en_US', ProfileId = p.Id, TimeZoneSidKey='America/Los_Angeles', UserName='standarduser@testorg.com.testorg');
System.runAs(u) {
Contact c = MyTestFactory.createContact('Chintan','Shah');
Test.startTest();
List<Contact> contacts = My.changeContactName( new List<Contact> {c} );
Test.stopTest();
for(Contact con : contacts ) {
System.assertEquals(con.firstName, con.firstName.toUpperCase(), ' firstName must be in upper case ' + con.firstName );
}
}
}
}
</code></pre>
<br /></div>
<div>
<br /></div>
<div>
<b>Data Factory:</b><br />
<b><br /></b>
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> /**
* Created by chshah on 9/14/2017.
*/
@isTest
public class MyTestFactory {
@TestVisible
private static Contact createContact(String firstName, String lastName) {
Contact c = new Contact(firstName = firstName, lastName = lastName );
insert c;
return c;
}
}
</code></pre>
<b><br /></b>
<b><br /></b></div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-63829248859900148952017-08-14T22:53:00.003-07:002017-08-14T22:53:41.320-07:00Salesforce send admin email for error/successSimple reference code for sending success/failure email in Salesforce<br />
<div>
<br /></div>
<div>
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> public static List<String> toAddresses = new List<String>();
/** to cache transaction data and reduce SOQLs
*/
public static List<String> getNotificationEmailAddress() {
if( toAddresses == null || toAddresses.size() == 0 ) {
Profile sysAdminProfile = [SELECT Id FROM Profile WHERE Name = 'System Administrator' limit 10];
List<User> sysAdmins = [SELECT id, Email FROM User WHERE ProfileId = :sysAdminProfile.id];
for( User sysAdmin : sysAdmins ) {
toAddresses.add ( sysAdmin.Email );
}
}
return toAddresses;
}
public static void sendMail(String subject, String body, List<String> recipients) {
try {
Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
message.toAddresses = recipients;
message.optOutPolicy = 'FILTER';
message.subject = subject;
message.plainTextBody = body;
Messaging.SingleEmailMessage[] messages = new List<Messaging.SingleEmailMessage> {message};
Messaging.SendEmailResult[] results = Messaging.sendEmail(messages);
if (results[0].success) {
System.debug(LoggingLevel.INFO, 'The email was sent successfully . to ' + recipients + ' subject ' + subject );
} else {
System.debug(LoggingLevel.ERROR, 'The email failed to send: ' + results[0].errors[0].message + ' to ' + recipients + ' subject ' + subject );
}
} catch(Exception e) {
System.debug(LoggingLevel.ERROR, 'The email failed to send: ' + ' to ' + recipients + ' subject ' + subject + ' body ' + body );
System.debug( e.getMessage() + '\n' + e.getStackTraceString() );
}
}
sendMail('test email','test body', new List<String> { 'chintanjshah@gmail.com' } );
sendMail('test email 2','test body 2', getNotificationEmailAddress() );
</code></pre>
<br /></div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-45818199422843544962017-08-07T19:57:00.000-07:002017-08-08T07:37:47.013-07:00Trigger framework with hierarchical kill switchesAn enhancement on existing matured trigger framework from <a href="https://krishhari.wordpress.com/2013/07/22/an-architecture-framework-to-handle-triggers-in-the-force-com-platform/" target="_blank">Hari K.</a><br />
<br />
It is very common scenario to disable trigger logic on certain user (e.g. batch) or profile or for entire org. I have enhanced the trigger framework and source code is available at :<br />
<br />
<a href="https://github.com/c-shah/trigger-framework">https://github.com/c-shah/trigger-framework</a><br />
<br />
How to use it :<br />
<br />
The framework already comes with one of the hiearchical setting called : TriggerFrameworkSettings__c.AllTriggersDisabled - this is false by default. If you need to disable all triggers, you can just check this checkbox. and all triggers will be disabled for a given user/profile/org.<br />
<br />
This method can also be extended for individual sObject.<br />
You can add <sObject<sobjecttype>>TriggerDisabled (e.g. AccountTriggerDisabled) custom setting under TriggerFrameworkSettings__c, and that setting will be dynamically be read for that sObject for a given user/profile/org. The trigger at individual sObject will be enabled/disabled based on flag value.</sobjecttype><br />
<br />Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-72554801344632471072017-06-23T07:05:00.000-07:002017-06-23T08:39:19.390-07:00Post Install Script FrameworkAs an ISV, many times we have to write post install scripts for upgrade. Salesforce provides facility to do that : https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_install_handler.htm<br />
<br />
However are quite a few limitations with this:<br />
<br />
<ul>
<li>It is hard to make sure if post install code is already executed or not</li>
<li>When developing upgrade script, we don't know what version it is going to be when it gets published</li>
<li>Hard to stop the execution or do the retry</li>
</ul>
<br />
<br />
Hence we tried to implement same approach using change set and integrated with Salesforce Post Install Handler. Changeset is industry wide practice used for a long time and here are some benefits:<br />
<br />
<br />
<ul>
<li>Multiple Post Install Scripts</li>
<li>Execution of Post Install Scripts in Order - and only once</li>
<li>On Error in any Script</li>
<ul>
<li>Stop/Halt the execution</li>
<li>Send Error Email</li>
</ul>
<li>On Successful completion of all scripts</li>
<ul>
<li>send summary email of all scripts</li>
</ul>
<li>Each script gets full set of governance limit</li>
<ul>
<li>In case of Salesforce, entire Batch is devoted to a given script</li>
</ul>
</ul>
<br />
<br />
With above points in mind, we created below framework, where ISV can just plugin in any post install script with minimal effort:<br />
<br />
<br />
<b><span style="font-size: large;">Framework</span></b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/PostInstall/framework1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/PostInstall/framework1.png" data-original-height="600" data-original-width="800" /></a></div>
<span class="Apple-tab-span" style="white-space: pre;"> </span><br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><br />
1. Entry point class - which implements Salesforce interface InstallHandler<br />
This class just calls PostInstallService.startService<br />
<br />
2. PostInstallService.startService<br />
This class scans for all the classes which extends PostInstallScriptTemplate in current namespace<br />
It inserts them into PostInstallScript__c object, if it doesn't exist already.<br />
<br />
3. PostInstallService.startService Calls PostInstallService.executeNextScript<br />
<br />
4. PostInstallService.executeNextScript<br />
Based on the data in PostInstallScript__c, it will call next PostInstallScriptTemplate(N)<br />
PostInstallScriptTemplate is batch interface so execution will be done asynchronous fashion<br />
[Note: There will be callback to PostInstallService, when PostInstallScriptTemplate(N) is completed/errored]<br />
<br />
5. Once the PostInstallScriptTemplate(N) is completed/errored<br />
It will update the PostInstallScript__c object with Status and Execution Log<br />
<br />
6. PostInstallScriptTemplate(N) will call back the framework PostInstallService.executeNextScript<br />
<br />
7. PostInstallService.executeNextScript<br />
based on data in PostInstallScript__c in, it will either:<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> a) Halt execution (if error)<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> b) Move on to the next script<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> c) Move on to finish if all of post install scripts are successfully completed<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><br />
8. A user interface to display currently pending/Completed/Errored Scripts along with Execution Logs<br />
<br />
9. A user interface provides facility to resubmit if Errored<span class="Apple-tab-span" style="white-space: pre;"> </span><br />
<br />
<br />
<b><span style="font-size: large;">Post Install Scripts</span></b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/PostInstall/script.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/PostInstall/script.png" data-original-height="600" data-original-width="800" /></a></div>
<br />
<br />
<br />
ISV can write Post Install script in two ways:<br />
<br />
<b><span style="font-size: large;">1)</span> </b>If Batch context is not needed, we can write Post install script as below,<br />
description, sequence number and execution log is stored in database.<br />
Actual post install logic is in executeScript method<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by cshah on <span style="color: #0000dd; font-weight: bold;">5</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">30</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2017.</span>
<span style="color: #333333;">*/</span>
public <span style="color: #008800; font-weight: bold;">with</span> sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">PostInstallScript1</span> extends PostInstallScriptTemplate {
private String executionLog;
public override void executeScript(Database<span style="color: #333333;">.</span>BatchableContext bc, List<span style="color: #333333;"><</span>SObject<span style="color: #333333;">></span> sObjects) {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript1 : execute : hoping to get executed only once '</span>);
executionLog <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'script 1 done. '</span>;
}
public override Integer getSequenceNumber() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript1 : getSequenceNumber '</span>);
<span style="color: #008800; font-weight: bold;">return</span> <span style="color: #0000dd; font-weight: bold;">1</span>;
}
public override String getExecutionLog() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript1 : getExecutionLog '</span>);
<span style="color: #008800; font-weight: bold;">return</span> executionLog;
}
public override String getDescription() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript1 : getDescription '</span>);
<span style="color: #008800; font-weight: bold;">return</span> <span style="background-color: #fff0f0;">'Script 1 Description '</span>;
}
}
</pre>
</div>
<br />
<br />
<b><span style="font-size: large;">2)</span> </b>If we need to query 50k+ records or update 10k+ records, we have to use Batch context and here is another way to write post install Script. In this case, we will need to override start and finish methods just like we do for any Salesforce Batch<br />
<div>
<br /></div>
<div>
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #333333;">/**</span>
<span style="color: #333333;">*</span> Created by cshah on <span style="color: #0000dd; font-weight: bold;">5</span><span style="color: #333333;">/</span><span style="color: #0000dd; font-weight: bold;">30</span><span style="color: #333333;">/</span><span style="color: #6600ee; font-weight: bold;">2017.</span>
<span style="color: #333333;">*/</span>
public without sharing <span style="color: #008800; font-weight: bold;">class</span> <span style="color: #bb0066; font-weight: bold;">PostInstallScript3</span> extends PostInstallScriptTemplate {
private String executionLog;
private Integer processedCount <span style="color: #333333;">=</span> <span style="color: #0000dd; font-weight: bold;">0</span>;
public override void executeScript(Database<span style="color: #333333;">.</span>BatchableContext bc, List<span style="color: #333333;"><</span>SObject<span style="color: #333333;">></span> sObjects) {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : execute : hoping to get executed only once '</span>);
executionLog <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">'script 3 done. '</span>;
processedCount <span style="color: #333333;">+=</span> sObjects<span style="color: #333333;">.</span>size();
}
public override Integer getSequenceNumber() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : getSequenceNumber '</span>);
<span style="color: #008800; font-weight: bold;">return</span> <span style="color: #0000dd; font-weight: bold;">3</span>;
}
public override String getExecutionLog() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : getExecutionLog '</span>);
executionLog <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">' Processed '</span> <span style="color: #333333;">+</span> processedCount <span style="color: #333333;">+</span> <span style="background-color: #fff0f0;">' records '</span>;
<span style="color: #008800; font-weight: bold;">return</span> executionLog;
}
public override String getDescription() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : getDescription '</span>);
<span style="color: #008800; font-weight: bold;">return</span> <span style="background-color: #fff0f0;">'Script 3 Description '</span>;
}
public override Integer getBatchSize() {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : getBatchSize '</span>);
<span style="color: #008800; font-weight: bold;">return</span> <span style="color: #0000dd; font-weight: bold;">1</span>;
}
public override Database<span style="color: #333333;">.</span>QueryLocator startScript(Database<span style="color: #333333;">.</span>BatchableContext bc) {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : startScript '</span>);
<span style="color: #008800; font-weight: bold;">return</span> Database<span style="color: #333333;">.</span>getQueryLocator(<span style="background-color: #fff0f0;">'select id, name from account limit 201'</span>);
}
public override void finishScript(Database<span style="color: #333333;">.</span>BatchableContext bc) {
System<span style="color: #333333;">.</span>debug(<span style="background-color: #fff0f0;">'PostInstallScript3 : finishScript '</span>);
}
}
</pre>
</div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b><span style="font-size: large;">User Interface</span></b></div>
<div>
<b><span style="font-size: large;"><br /></span></b></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.springsoa.com/blogs/PostInstall/vf.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.springsoa.com/blogs/PostInstall/vf.png" data-original-height="160" data-original-width="800" height="124" width="640" /></a></div>
<div>
<b><span style="font-size: large;"><br /></span></b></div>
<div>
User interface allows to view the post install script, and their execution log. </div>
<div>
It allows to resubmit in case of error</div>
<div>
<b><span style="font-size: large;"><br /></span></b>
<b><span style="font-size: large;">Source code </span></b><br />
<br />
1. It can be found at github : https://github.com/c-shah/PostInstallScriptFramework<br />
2. Or as unmanaged package : https://login.salesforce.com/packaging/installPackage.apexp?p0=04tf400000099bW</div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-66169903526888672662017-05-01T16:13:00.001-07:002017-11-20T10:12:12.325-08:00OData/Heroku with Salesforce - Integrate differentlyAs we usually come across, below is standard pattern when we integrate external app talking to Salesforce or Salesforce talking to external application.
We use different API to talk to Salesforce and use workflow outbound, or rest/soap calls to make outbound call.<a href="https://www.springsoa.com/blogs/odata/existing_integration.png" imageanchor="1"><img border="0" src="https://www.springsoa.com/blogs/odata/existing_integration.png" height="367" width="640" /> </a><br />
<br />
Below is different approach using OData, and in many cases it can make the integration very simple and minimal to no code on salesforce.<br />
<br />
<span style="font-size: large;"><b>What is OData?</b></span><br />
It is a standard way to represent data. Detail can be found at : http://www.odata.org/documentation/ however, at very high level it is web service way of representing data like relational database. Main features:<br />
<br />
<b>Metadata: </b><br />
There is metadata ($metdata) to get information about all schemas, tables, columns, and procedures. <br />
<br />
<b>SQL like operation</b><br />
We could do SQL like operation instead of creating new operation for each type (e.g. get employee by first name, by last name, etc..) <br />
<br />
Here is naming convention (left: Classic Relational Database, right: OData 4.0 naming)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/odata/odata_rdbms.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://www.springsoa.com/blogs/odata/odata_rdbms.png" height="336" width="640" /></a></div>
<br />
<br />
<span style="font-size: large;"><b>Heroku</b></span><br />
Heroku is very well known and a lot of documentation could be found at https://www.heroku.com/, so would be focus only on two items:<br />
<br />
<b>Heroku Connect</b><br />
Single or bi directional link to Salesforce tables/fields to Heroku tables/fields. Any changes to Salesforce is migrated to Heroku Postgres database over extremely fast and efficient SQL link. And if bidirectional link is configured, any changes on Heroku is posted back to Salesforce.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/odata/heroku_connect.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://www.springsoa.com/blogs/odata/heroku_connect.png" height="324" width="640" /></a></div>
<br />
<br />
<br />
<b>Heroku App Engine</b><br />
We could host custom Java/Node and other supported language application on Heroku with just click of a button. Hence, I wrote custom Java app using Apache Olingo to host on Heroku platform.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/odata/Olingo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://www.springsoa.com/blogs/odata/Olingo.png" height="400" width="640" /></a></div>
<br />
This app generates metadata and connects to heroku postgres to get data and exposes everything as OData service using Apache Olingo framework.<br />
<br />
<b>Notes:</b><br />
<ul>
<li>Had to use Tomcat (instead of http://sparkjava.com) as Olingo requires servlet </li>
<li>Need to implement two interfaces</li>
<ul>
<li>Metadata Interface (to render schema, entity, entity set)</li>
<li>Data Processor (to fetch and return the data) </li>
</ul>
<li>Had to remove http header accept, as causing issue with Apache Olingo</li>
</ul>
<br />
<span style="font-size: large;">Putting everything together</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/odata/newIntegrationPattern.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://www.springsoa.com/blogs/odata/newIntegrationPattern.png" height="368" width="640" /></a></div>
<ul>
<li>Once it is exposed as OData Service, Salesforce can connect and all the EntitySet exposed in OData would be available as external object on Salesforce (ends with __x)</li>
<li>We can do SOQL, SOSL, indirect lookup on those Salesforce Object </li>
<li>This would be zero code on Salesforce and on Heroku side, we can get data from Cache, PostGres or External App using Rest or SOAP api</li>
</ul>
<br />
On Salesforce side, it would be just providing URL and it will automatically list down all objects and it will be able to create objects as selected.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/odata/SFside.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://www.springsoa.com/blogs/odata/SFside.png" height="392" width="640" /></a></div>
<br />
<br />
<br />
<span style="font-size: large;"><b>Final Take</b></span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.springsoa.com/blogs/odata/NewPattern2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://www.springsoa.com/blogs/odata/NewPattern2.png" height="350" width="640" /></a></div>
<br />
<br />
<ul>
<li> Reiterating the first diagram, Heroku Connect provides alternative to accessing and updating data via API and it is super helpful if app is living on Heroku or would like to connect directly to postgres database</li>
<li>Salesforce can connect to external app via Odata on heroku, and that would reduce the code on Salesforce org and promote more click over code approach</li>
</ul>
<br />
Code for Apache Olingo implementation can be found at :<br />
<a href="https://github.com/spring-work/odata" target="_blank">https://github.com/spring-work/odata</a><br />
<br />
Heroku App:<br />
<a href="http://odata-cshah.herokuapp.com/odata.svc/" target="_blank">http://odata-cshah.herokuapp.com/odata.svc/ </a><br />
<br />
<ul>
</ul>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com0tag:blogger.com,1999:blog-1459511178626634251.post-33533621094424207372017-02-02T11:53:00.000-08:002017-02-02T12:07:49.264-08:00Call Salesforce REST API from ApexNothing new but just below example helps make call to Salesforce REST API from Apex. If you need to know Org limits at run time, most them are available via Limits call, but some are available via rest api at : /services/data/v37.0/limits (e.g. daily async limits, email or bulk email limits, etc.) and it is easy to get those information from workbench.developerforce.com, however if you need to add this to the code, below is the code:<br />
<div>
<br /></div>
<div>
<br /></div>
<div>
<b>Add your Org URL to remote site setting</b><br />
<div>
Note: if you don't know, then either you can look at browser or via below call:</div>
<div>
System.debug( URL.getSalesforceBaseUrl().toExternalForm() );</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b>Run Anonymous block below, which is broken down into three pieces</b></div>
</div>
<div>
1. Get the base URL</div>
<div>
2. Get Auth Token</div>
<div>
3. Actual HTTP Request</div>
<div>
<br />
<pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;padding:0px;color:#000000;text-align:left;line-height:20px;"><code style="color:#000000;word-wrap:normal;"> /* 1. get base URL */
public static String getSalesforceInstanceUrl() {
<mark>return URL.getSalesforceBaseUrl().toExternalForm();</mark>
}
public static String getRestResponse(String url) {
HttpRequest httpRequest = new HttpRequest();
httpRequest.setEndpoint(url);
httpRequest.setMethod('GET');
/* 2. set the auth token */
<mark>httpRequest.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());</mark>
<mark>httpRequest.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID());</mark>
try {
Http http = new Http();
/* initiate the actual call */
<mark>HttpResponse httpResponse = http.send(httpRequest);</mark>
if (httpResponse.getStatusCode() == 200 ) {
return JSON.serializePretty( JSON.deserializeUntyped(httpResponse.getBody()) );
} else {
System.debug(' httpResponse ' + httpResponse.getBody() );
throw new CalloutException( httpResponse.getBody() );
}
} catch( System.Exception e) {
System.debug('ERROR: '+ e);
throw e;
}
return null;
}
System.debug(' -- limit method code block -- : start ');
String baseUrl = getSalesforceInstanceUrl();
System.debug(' -- baseUrl-- : ' + baseUrl );
String limitsUrl = baseUrl + '/services/data/v37.0/limits';
System.debug(' -- limitsUrl-- : ' + limitsUrl );
String response = getRestResponse(limitsUrl);
System.debug(' -- response-- : ' + response );
</code></pre>
</div>
Chintan Shahhttp://www.blogger.com/profile/02288969318164103126noreply@blogger.com1