Archive

Author Archive

Salesforce consuming Messages on iFrame from Spring Boot App

July 24, 2020 Leave a comment

In the previous blog (https://codesilo.wordpress.com/2020/07/24/salesforce-canvas-and-spring-boot-app/) we saw how we can have a message posted from an application in Canvas to the Lightning component. An alternate is to use an iFrame. The advantage of Canvas is that it provides secure communication between the iFrame and Salesforce but sometimes the application cannot have an implementation that ties to the Canvas SDK. iFrame can be used in these cases.
There are a few things that need to be changed with respect to the previous blog. We will simplify the Spring boot application (we don’t need to handle a signed_request). We will also change the Lightning component to use an iFrame and add CSP Definition in Salesforce.

For the Spring Boot back end implementation, we will keep the same implementation but will make it simpler – will change the method from POST to GET. This call to http://localhost:8080 will return us the same page “index.html”. But in the index.html, we will remove any reference to the canvas sdk. Here is what the controller will look like..

package com.wordpress.codesilo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {
	@RequestMapping("/")
	public String index() {
		return "index";
	}	
}

For now, we will keep the index.html really simple.

<html>
<body>
	<h1>Main Index Page</h1>
	<p> Canvas Page Content</p>
	
	<input type="text"/>
	<button id="ctxlink" >Send Message</button>
</body>
</html>

Now, on the Lightning component, we will remove the canvasApp tag and add an iFrame tag. The component will look as follows

<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome">
	<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <lightning:card>
        <iframe aura:id="vfFrame" src="http://localhost:8080/" />
    </lightning:card>
</aura:component>

Our controller on Lightning looks as follows for now ..

({
	doInit : function(component, event, helper) {
		console.log('Loaded component');
	}
})

Since we are using Spring boot without security (https/ssl), if we load the page with the Lightning component now, we will get an error because of cross site security. On inspecting the response on the browser, you might be able to see the following message “Refused to frame because it violates the following Content Security Policy directive: “frame-src” “. The UI (Lightning Component) will show the following within the iFrame “Requests to the server have been blocked by an extension.”
For this, what we need to do is add a CSP Trusted Site Definition. Go to Setup | CSP Trusted Sites on Salesforce Setup and add a new CSP Trusted Site Definition. The setting will look similar to the following. For our example, we need to enable “frame-src” but I have enabled all here.

Now when we load the page with the iFrame, it should show the application. Note: In the real world, you might want to use https to have better security. If https is the way to go in your case, you won’t need the Security Definition above.

We will now add an event publish on button click on index.html. This is going to publish an event to the parent (Lightning Component), so that we can listen to it there. Our index.html file now looks like this:

<html>
<head>
	<script type="text/javascript">
	window.onload = (event) => {
		var ctxlink = document.getElementById("ctxlink");
		ctxlink.onclick = function(){
			var domain = "https://your-salesforce-instance-here";
			var data =  {
    			            name: "iFrame.message",
    			            payload: "this is message from non canvas message post" 
    			        };
	    	parent.postMessage(data, domain);
	    	console.log("message posted");
		};
		
	};
	</script>
	
</head>
<body>
	<h1>Main Index Page</h1>
	<p> Canvas Page Content</p>
	
	<input type="text"/>
	<button id="ctxlink" >Send Message</button>
</body>
</html>

Note: Change the code above and replace “your-salesforce-instance-here” with your Salesforce instance name.

Change the Lightning Component so that we can listen to the event on the controller. The Lightning Component controller looks like this :

({
	doInit : function(component, event, helper) {
		window.addEventListener('message', function (event) {
            console.log('In Event Listener in Lightning');
			if(event.data &&
               event.data.name=="iFrame.message"){
                console.log(event.data.name);
				console.log(event.data.payload);
            }
        }, false);
	}
})

What this does is listen to the event from the index.html on our Spring boot app. The data is the data that is published from the script there. So, the event.data.name and event.data.payload should give the respective data points, name and payload, from the data published.

Salesforce Canvas and Spring Boot App.

July 24, 2020 Leave a comment

Salesforce Lightning provides 2 ways to embed an external application in Salesforce. One could use a Canvas App, or, embed an iFrame in the Lightning Component. When using Canvas, there are a couple of ways the users could be authenticated in order to use the sdk (admin approved and self authorized users). The documentation is interleaved and did not provide a clear path of implementation. In this blog here, I have tried to cover one way to use Spring Boot and use a signed request from Canvas for the authentication.

The blog here creates a simple Spring Boot application with ssl enabled and Thymeleaf as the template. The focus will be on getting this application in the Canvas app and working our way through the signed request authentication (Thymeleaf could be replaced by other solutions. Similarly, Spring boot could be replaced by other frameworks if desired)

Here are the steps we will take to set this up.

  1. Create a Canvas App in Salesforce
  2. Create a basic Spring boot app sends a basic template back. (This will mimic a form but will not have the form wired to the application)
  3. Adding the application to the Canvas on a Lightning Component and page
  4. Refreshing a signed request.
  5. Decoding and verifying a signed request.
  6. Looking at the Canvas Request object
  7. Creating an event from the Application
  8. Consuming the event on the Lightning Component
  1. Creating the Canvas App in Salesforce
    Go to Setup | Apps | App Manager in Salesforce and click on the “New Connected App” button on the top right of the page.
    In the “New Connected App” page fill the following
    Connected App Name: My Spring Boot Canvas App
    API Name : (Will be filled automatically) My_Spring_Boot_Canvas_App
    Contact Email: Your-email-here
    Click on “Enable OAuth Settings”, add the callback URL as http://localhost:8080 and provide “Full Access (full)” in the “Available OAuth Scopes” (Note: In the production environment, you might want to restrict the privileges here)
    Skip to “Canvas App Settings” and check the checkbox “Canvas”
    Fill the following fields that show up in the “Canvas App Settings” section
    Canvas App Url: http://localhost:8080
    Access Method: Signed Request(POST)
    Locations: “Lightning Component” (We will put the Canvas app in the Lightning Component for now)

Once the App is created, locate your App in the App Manager and select “Manage” on the App. This will take you to a screen where you can provide your user permissions to access this App. In this page, go down to “Profiles” section, click on “Manage Profiles” and add your users Profile to the list. (Note: Permission Sets could be used here as an alternative)

  1. Creating a Spring Starter Project with Thymeleaf.
    In my example I use the Spring Starter from Spring STS IDE to create a base project for Spring Boot. The same could be done using Spring Initializr (https://start.spring.io/)
    We will create a Controller class as follows. This will have a request with method type as post and will accept a request parameter. This request parameter will have the signed request from Salesforce when the Canvas App loads.
package com.wordpress.codesilo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {

	@RequestMapping(value = "/", method = RequestMethod.POST)
	public String index(@RequestBody String signedRequest) {
		System.out.println(signedRequest);
		return "index";
	}
	
}

For now, we will send a simple index page back – using thymeleaf template. For this the index.html will be in the “templates” folder in the “resources” of the project. Here is what our index.html page looks like. We will change this later.

<html>
<body>
	<h1>Main Index Page</h1>
	<p> Canvas Page Content</p>
	
	<input type="text"/>
	<button id="ctxlink" >Some button</button>
</body>
</html>

At this point if we start the server and call the following endpoint http://localhost:8080/ from the browser, we should get a method not supported error page. (405 status). But if we use Postman to call the same endpoint but as a “POST” request with some dummy body payload, we should get back the index.html content in the response.

  1. Adding the application to the Canvas on a Lightning Component and page
    Create a Lightning Component with the name “SpringBootCanvasApp” and add this on the cmp file.
<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome">
	<lightning:card>
    <force:canvasApp developerName="My_Spring_Boot_Canvas_App " maxHeight="200" maxWidth="200"/>
        </lightning:card>
</aura:component>

This creates the Lightning Component will we will place in a Lightning page next. This component contains the CanvasApp (My_Spring_Boot_Canvas_App) that points to the Spring Boot app we created earlier.

Go to Setup | Lightning App Builder and “Create a new Lightning Page”
Select “App Page” and give the label in the next screen as “My_Spring_Boot_Canvas_App”
Select “One region” in the next step and click “Finish”
This will take us to the screen where we can select out previously created Lightning Component “SpringBootCanvasApp”. Drop this app on the page and “Activate” the page after “Save”

Now add this Lighning page on the Navigation on one of your Apps in Salesforce. When we go to this page “My_Spring_Boot_Canvas_App” , we should be able to see the Spring boot application load within the Lightning Component in that page. (Make sure that your Spring Boot server is still running and not shutdown). On the Spring boot server, you should be able to see the signed_request logged on the console.

  1. Canvas SDK and Refreshing a signed request.
    Canvas provides a framework (Canvas SDK) that can be used to perform various operations on Salesforce. In our next step we will see how we can access the sdk and then use it to get the signed request within the UI (javascript) part of the application. We will, for now, provide inline javascript within our Thymeleaf teamplate. These could be replaced with your choice of frameworks/libraries/templates. The basics remain the same.

Canvas provides a framework (Canvas SDK) that can be used to perform various operations on Salesforce. In our next step we will see how we can access the sdk and then use it to get the signed request within the UI (javascript) part of the application. We will, for now, provide inline javascript within our Thymeleaf teamplate. These could be replaced with your choice of frameworks/libraries/templates. The basics remain the same.

Here is the final version of what our index.html template looks like now. We have done the following .. on the load of the page in the browser, we will add an onclick event on the button. The click of the button, calls the refreshSignedRequest on the Canvas SDK (this is the current way to get the signed request within the UI). On getting a successful response, it logs the signedRequest on the console else it logs the error status. Note: Replace the “your-salesforce-instance-name-here” with the instance name of your Salesforce and correct the version number as necessary.

<html>
<head>
	<script type="text/javascript" src="<your-salesforce-instance-name-here>/canvas/sdk/js/48.0/canvas-all.js"></script>
	<script type="text/javascript">
	window.onload = (event) => {
		var ctxlink = Sfdc.canvas.byId("ctxlink");
		ctxlink.onclick = function(){
			Sfdc.canvas.client.refreshSignedRequest(function(data) {
				if(data.status==200){
					var signedRequest = data.payload.response;
					console.log(signedRequest);
				}
				else{
					console.log('Status received != 200. Status is ' + data.status);
				}
			});
		}
	};
	
	</script>
	
</head>
<body>
	<h1>Main Index Page</h1>
	<p> Canvas Page Content</p>
	
	<input type="text"/>
	<button id="ctxlink" >Some button</button>
</body>
</html>
  1. Decoding and verifying a signed request.
    So, what does the signed request of Canvas consist of ? The details of what the signed request is can be found at the end of this documentation here (https://developer.salesforce.com/docs/atlas.en-us.platform_connect.meta/platform_connect/canvas_app_signed_req_authentication.htm). Verifying and decoding of the signed request can be done in this way : https://developer.salesforce.com/docs/atlas.en-us.platform_connect.meta/platform_connect/canvas_app_unsigning_code_example.htm

We will create a decoder class Decoder.class with the following code ..

package com.wordpress.codesilo.controller;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;

@Component
public class Decoder {

	public void decodeSignedRequest(String signedRequest) {
		try {
			String signedRequestVal = signedRequest.split("signed_request=")[1];
			
			String[] signedRequestArray = signedRequestVal.split("[.]");
			String clientSecret = "your-client-secret-from-canvas-connected-app-here";
			String encodedSignature = signedRequestArray[0];
			String endcodedEnvelope = signedRequestArray[1];

			Mac sha256HMAC = Mac.getInstance("HMACSHA256");
			SecretKey hmacKey = new SecretKeySpec(clientSecret.getBytes(), "HMACSHA256");
			sha256HMAC.init(hmacKey);

			byte[] digest = sha256HMAC.doFinal(endcodedEnvelope.getBytes("UTF-8"));
			byte[] decode_sig = new Base64(true).decode(encodedSignature);

			if (Arrays.equals(digest, decode_sig)) {
				System.out.println("Valid signed request found");
			} else {
				System.out.println("Invalid signed request found");
			}

			// The following is the CanvasRequest object generated from Salesforce
			String jsonEnvelope = new String(new Base64(true).decode(endcodedEnvelope));
			System.out.println(jsonEnvelope);

		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalStateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

Replace the client secret from the secret from your instance of Salesforce above.

We will then call this decoder.decodeSignedRequest method from our main controller class. We will change the controller class a bit to make sure the request body is URL decoded. This is what the class looks like..

@Controller
public class HomeController {

	@Autowired
	private Decoder decoder;
	
	@RequestMapping(value = "/", method = RequestMethod.POST)
	public String index(@RequestBody String signedRequest) {
		try {
			String signedRequestDecoded = URLDecoder.decode(signedRequest, StandardCharsets.UTF_8.name());
			System.out.println(signedRequestDecoded);
			decoder.decodeSignedRequest(signedRequestDecoded);
		}
		catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return "index";
	}
	
}

Note: The code shown does not cover areas like error handling or other best practices. That is left for the reader to add.

  1. Looking at the Canvas Request object
    In the Decoder class above, we log a jsonEnvelope string. This is the String version of the CanvasRequest object. (https://developer.salesforce.com/docs/atlas.en-us.platform_connect.meta/platform_connect/canvas_request_object.htm). Something like Jackson can be used to parse this into an Object.
  1. Creating an event from the Application
    Now we will take a look at a simple case of rasing an event from the Spring boot application and consuming that on the Lightning component allowing a client – client communication between the Canvas App and the parent (the Lightning component).
    We will change the index.html file to parse the signedRequest to an Object, get a “client” from it and then use the “publish” method on the sdk to publish a message. Here is what the final “onclick” method will look like.
ctxlink.onclick = function(){
			Sfdc.canvas.client.refreshSignedRequest(function(data) {
				if(data.status==200){
					var signedRequestResponse = data.payload.response;
					console.log(signedRequestResponse);
					
					var signedReqObj = JSON.parse(Sfdc.canvas.decode(signedRequestResponse.split('.')[1]));
		    	    var client = signedReqObj.client;

					Sfdc.canvas.client.publish(
		    	            client,
		    	            {name : "mynamespace.statusChanged", payload : {status : 'Message Sent'}});
					
				}
				else{
					console.log('Status received != 200. Status is ' + data.status);
				}
			});
		};
  1. Consuming the event on the Lightning Component
    Now on the Lightning Component on Salesforce we will make a couple of changes to listen to the Message. We will first add onCanvasAppLoad attribute to the component canvasApp. The component will now look as follows:
<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome">
	<lightning:card>
    <force:canvasApp developerName="My_Spring_Boot_Canvas_App" maxHeight="200" maxWidth="200" onCanvasAppLoad="{!c.onCanvasLoad}"/>
        </lightning:card>
</aura:component>

Now, we will add a controller and add the following code to the controller.

({
	onCanvasLoad : function(component, event, helper) {
		console.log('Canvas Loaded');
        window.addEventListener('message', function (event) {
            var data = JSON.parse(event.data);
            if (data.targetModule === 'Canvas' && 
                data.body && 
                data.body.event && 
                data.body.event.name === 'mynamespace.statusChanged')
            console.log('payload from canvas app', data.body.event.payload);
        }, false);
	}
})

Now, when we go to the Canvas App and click on the button, the console should log the messages from the Lightning App.

Update: Posting messages using iFrame instead of Canvas can be found in this blog here: (https://codesilo.wordpress.com/2020/07/24/salesforce-consuming-messages-on-iframe-from-spring-boot-app/)

References:

https://salesforce.stackexchange.com/questions/131245/how-to-fire-an-event-from-a-canvas-app-to-a-lightning-component

https://salesforce.stackexchange.com/questions/106538/canvas-app-signedrequest-is-null-on-postback

https://salesforce.stackexchange.com/questions/76869/canvas-signed-request-sr-in-visualforce

Salesforce List and Map on Lightning Component

When a lightning component needs to call Apex to have the controller perform some operation, it sends a response back. Many times we need to have this in the form of a List or a Map. For example, if we need to get a response back that shows us a list of records that need to displayed on a table on the component, or, we need grouped Lists based on an attribute (Map) – the group can denote the key of the map and the List of Objects the value in the Map.

Salesforce Lightning currently defines a way to iterate though a List on the component but the iteration of the Map does not work as expected (Nested iteration of the List value within the Map). We will go over an example on how to iterate over values in such a scenario.

For this explanation, we will take an example where we return a List of Opportunities (Limit n) in one case (return will be List<Opportunity>). In another case we will return a Map of Opportunities grouped by the Stage Name. (return will be Map<String, List<Opportunity>>).

Setting up the Lightning App

Goto Setup | Lightning App builder. Click on “New” on Lightning Pages and then select “App Page” from the list in the window that opens. We will give it a name (in my example the name I have given is “Testing Map and List”) and select a “One Region” page to keep things simple.
We will “Save” the page and “Activate” it for now.

Setting up the Controller Apex class

Before we change the content of the page, we will create our Apex Controller and the component that needs to go on the page.

Here is how our Apex controller looks like.

public with sharing class MapAndListFunctions{
    
    @AuraEnabled
    public static List<Opportunity> getOpportunityList(){
        List<Opportunity> opportunityList = new List<Opportunity>();
        opportunityList = [select Id, Name, StageName, CreatedDate from Opportunity limit 30];
        return opportunityList;
    }
    
    @AuraEnabled
    public static Map<String, List<Opportunity>> getOpportunityMap(){
        Map<String, List<Opportunity>> opportunityMap = new Map<String, List<Opportunity>>();
        List<Opportunity> listOfOpportunities = getOpportunityList();
        for(Opportunity opp: listOfOpportunities){
            List<Opportunity> opportunitiesForStage = opportunityMap.get(opp.stageName);
            if(opportunitiesForStage != null && opportunitiesForStage.size()> 0){
                opportunitiesForStage.add(opp);
                opportunityMap.put(opp.stageName, opportunitiesForStage);
            }
            else{
             opportunityMap.put(opp.stageName, new List<Opportunity>{opp});   
            }
        }
        return opportunityMap;
    }
}

The controller class has 2 simple methods. One returns a list of Opportunities (getOpportunityList) while the other returns a map which has list of Opportunities grouped by their Stage Name. The key in the Map is the Stage Name while the value is a List of Opportunities that have that particular Stage.

Building the component for List iteration

Let’s start building our component. We will focus most of the discussion here –
For the initial iteration, we will build the component that will call the Apex controller to get the List of Opportunities. Later we will change the code to add the implementation for getting and displaying the Map.

Our first cut of the component looks like this –

<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="MapAndListFunctions">
	<aura:handler name="init" value="{!this}" action="{!c.init}"/>
	<aura:attribute name="opportunityList" type="List" />	
    
    <lightning:card>
        <table class="slds-table slds-table--bordered">

        <thead>
			<tr class="slds-text-heading--label">
                <th scope="col">
                	<span class="slds-truncate">Id</span>
                </th>
				<th scope="col">
                	<span class="slds-truncate">Name</span>
                </th>
				<th scope="col">
                	<span class="slds-truncate">Stage Name</span>
                </th>
                <th scope="col">
                	<span class="slds-truncate">Created Date</span>
                </th>
            </tr>

        </thead>
        <aura:iteration items="{!v.opportunityList}" var="opportunity">
			<tbody>
				<tr class="slds-hint-parent">
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.Id}</span>
                    </td>
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.Name}</span>
                    </td>
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.StageName}</span>
                    </td>
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.CreatedDate}</span>
                    </td>
                </tr>
            </tbody>
        </aura:iteration>
        </table>
    </lightning:card>
</aura:component>

What the component does is look at the OpportunityList result set and iterates them into a table. The table can be replaced by a DataTable or some other form of display.

The controller for the component is as follows

({
	init : function(component, event, helper) {
		var action = component.get("c.getOpportunityList");
		action.setCallback(this, function (response) {
        	var state = response.getState();
            console.log(response.getReturnValue());
            if (state === "SUCCESS") {
				var opportunityList = response.getReturnValue();
				component.set("v.opportunityList", opportunityList);
            }
        });

		$A.enqueueAction(action);
	}
})

Adding the component to the page

Now we will go back to the Lightning App (page) we created earlier and add the component we built on the page (Note: If you are setting up the Org for a custom component for the first time, make sure that the domain of the Org is configured and deployed.)

Once the Lightning page is updated with the component, go to the App Launcher and look for the Lightning application we built. (in my example the name is “Testing Map and List”). The page should look similar to the following, depending on the records present in the Opportunity object.

When we look at the component above, our intuition says that in order to have a Map to be iterated, the format should be something similar to a List except that the type is replaced with a Map instead of a List. Something similar to …

<aura:attribute name="opportunityStageNameMap" type="Map" />	

However, the code above does not provide the desired results when a Map is returned from the Apex controller. It is not possible to iterate over the List of Opportunities by iterating over the keys using aura:iterate. (a design flaw in map i guess)

In order to do this, a workaround needs to be put in place.

Add logic to the Component controller for the Map

We will add the following implementation to the controller. This new action will call the getOpportunityMap on the Apex controller and get the Map that has the “StageName” as the key and a List of Opportunities mapped to the StageName.

var action = component.get("c.getOpportunityMap");
		action.setCallback(this, function (response) {
        	var state = response.getState();
            console.log(response.getReturnValue());
            if (state === "SUCCESS") {
				var mapResult = response.getReturnValue();
                console.log(mapResult);
                var results = [];
				for ( var key in mapResult ) {
	                results.push({value:mapResult[key], key:key});
                }
                component.set("v.mapResults", results);
            }
        });

		$A.enqueueAction(action);

The following code (from the controller code above) allows us to store the Map in a List in the component and allow us to access this data structure using Key and Value in the Component. The value will be a List of Opportunities than can be iterated on it’s own.

var results = [];
				for ( var key in mapResult ) {
	                results.push({value:mapResult[key], key:key});
                }
                component.set("v.mapResults", results);

The final Component and Controller code are as following. The Map results are shown as an accordion in the second section of the component (in the second lightning:card).

Component Code

<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="MapAndListFunctions">
	<aura:handler name="init" value="{!this}" action="{!c.init}"/>
	<aura:attribute name="opportunityList" type="List" />	
	<aura:attribute name="mapResults" type="List" />	
    
    
    <lightning:card>
        <table class="slds-table slds-table--bordered">

        <thead>
			<tr class="slds-text-heading--label">
                <th scope="col">
                	<span class="slds-truncate">Id</span>
                </th>
				<th scope="col">
                	<span class="slds-truncate">Name</span>
                </th>
				<th scope="col">
                	<span class="slds-truncate">Stage Name</span>
                </th>
                <th scope="col">
                	<span class="slds-truncate">Created Date</span>
                </th>
            </tr>

        </thead>
        <aura:iteration items="{!v.opportunityList}" var="opportunity">
			<tbody>
				<tr class="slds-hint-parent">
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.Id}</span>
                    </td>
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.Name}</span>
                    </td>
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.StageName}</span>
                    </td>
                    <td data-label="stage">
                    	<span class="slds-truncate">{!opportunity.CreatedDate}</span>
                    </td>
                </tr>
            </tbody>
        </aura:iteration>
        </table>
    </lightning:card>
    
    <lightning:card>
		<lightning:accordion aura:id="accordion">
			<aura:iteration items="{!v.mapResults}" var="mapEntry" indexVar="key">
				<lightning:accordionSection name="{!mapEntry.key}" label="{!mapEntry.key}">
                    <table class="slds-table slds-table--bordered">

                        <thead>
                            <tr class="slds-text-heading--label">
                                <th scope="col">
                                    <span class="slds-truncate">Id</span>
                                </th>
                                <th scope="col">
                                    <span class="slds-truncate">Name</span>
                                </th>
                                <th scope="col">
                                    <span class="slds-truncate">Stage Name</span>
                                </th>
                                <th scope="col">
                                    <span class="slds-truncate">Created Date</span>
                                </th>
                            </tr>
                
                        </thead>
                        <aura:iteration items="{!mapEntry.value}" var="opportunity">
                            <tbody>
                                <tr class="slds-hint-parent">
                                    <td data-label="stage">
                                        <span class="slds-truncate">{!opportunity.Id}</span>
                                    </td>
                                    <td data-label="stage">
                                        <span class="slds-truncate">{!opportunity.Name}</span>
                                    </td>
                                    <td data-label="stage">
                                        <span class="slds-truncate">{!opportunity.StageName}</span>
                                    </td>
                                    <td data-label="stage">
                                        <span class="slds-truncate">{!opportunity.CreatedDate}</span>
                                    </td>
                                </tr>
                            </tbody>
                        </aura:iteration>
                        </table>
                </lightning:accordionSection>
	        </aura:iteration>
        </lightning:accordion>
    </lightning:card>
</aura:component>

Component Controller Code

({
	init : function(component, event, helper) {
		var action = component.get("c.getOpportunityList");
		action.setCallback(this, function (response) {
        	var state = response.getState();
            console.log(response.getReturnValue());
            if (state === "SUCCESS") {
				var opportunityList = response.getReturnValue();
				component.set("v.opportunityList", opportunityList);
            }
        });

		$A.enqueueAction(action);
        
        var action = component.get("c.getOpportunityMap");
		action.setCallback(this, function (response) {
        	var state = response.getState();
            console.log(response.getReturnValue());
            if (state === "SUCCESS") {
				var mapResult = response.getReturnValue();
                console.log(mapResult);
                var results = [];
				for ( var key in mapResult ) {
	                results.push({value:mapResult[key], key:key});
                }
                component.set("v.mapResults", results);
            }
        });

		$A.enqueueAction(action);
	}
})

Here is how the final Accordion with the Map data looks :

Swagger 2 /Open API Specification on Spring Boot

January 24, 2018 Leave a comment

Swagger 2 ,or, Open API Specification is a definition used for REST APIs. (https://swagger.io/specification/). We will in this blog see how to implement the Open API Specification on a Spring Boot application. The Open API spec allows us not only to provide human/machine readable mapping but allow us to use tools on it for creating other implementations (stubs for the services for example)

Here are some reasons how this can be helpful
1. Guiding principles for the API
2. Allows us to create the API from top-down or bottom-up approach
3. Can be easily understood by developers and non-developers
4. Machine readable as well – can be used for tooling and automation

In addition to above Google Cloud Platform Endpoints requires that the API implement this spec. We will see Endpoints in the next blog.

Before we get to the specifics of implementing the Open API spec on our application, we will create a simple Spring Boot Application (See the “Creating the Spring Boot Applicaton” section in this blog https://codesilo.wordpress.com/2017/12/26/spring-boot-rest-api-integration-with-salesforce/ )

There are various libraries that implement the Open API specification. We will use SpringFox (http://springfox.github.io/springfox/). We will add the following dependencies on the pom.xml file

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.1</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
    <scope>compile</scope>
</dependency>

Next we add the config class for Open API in the main package.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class OpenAPIConfig {

    @Bean
    public Docket productApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select().apis(RequestHandlerSelectors.basePackage("com.wordpress.codesilo"))
                .paths(PathSelectors.ant("/root/*"))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("My Open API Implementation")
                .description("Application for Open API implementation")
                .version("2.0")
                .build();
    }
}

The package name and the PathSelectors can vary depending on the project. Also, the other values are configurable and will display on Swagger UI page.

Next we will implement a simple controller class in our Spring Boot application. Our controller class has a request mapping for /root and returns back a simple message.

package com.wordpress.codesilo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MainController {

	@RequestMapping(value ="/root", response = MainMessage.class)
	public MainMessage getMessage(){
		return new MainMessage("This is the root controller !!");
	}

}

The MainMessage is a simple POJO with a greeting attribute of type String.

We now will annotate the controllers and methods. Here is an example of annotating the class

@Api(tags={"Root Controller"}, value="Open API for testing", consumes="null")

Annotation of the method can be done in the following way

@ApiOperation(value = "Returns a message", response = MainMessage.class)
@ApiResponses(value = {
            @ApiResponse(code = 200, message = "Greeting is successfully sent"),
            @ApiResponse(code = 401, message = "You are not authorized to view the resource"),
            @ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
            @ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
            }
    )

Annotations are also added on the model class. After the annotations, the classes look as shown below.

package com.wordpress.codesilo.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(value="Main Response", description="Sample model")
public class MainMessage {

	@ApiModelProperty(value = "Greeting/Message", required=true)
	private String greeting;

	public MainMessage(){
	}

	public MainMessage(String greeting){
		this.greeting = greeting;
	}
	public String getGreeting() {
		return greeting;
	}

	public void setGreeting(String greeting) {
		this.greeting = greeting;
	}
}
package com.wordpress.codesilo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.wordpress.codesilo.model.MainMessage;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

@RestController
@RequestMapping("/root")
@Api(tags={"Root Controller"}, value="Open API for testing", consumes="null")
public class MainController {

	@ApiOperation(value = "Returns a message", response = MainMessage.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Greeting is successfully sent"),
            @ApiResponse(code = 401, message = "You are not authorized to view the resource"),
            @ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
            @ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
            }
    )
	@RequestMapping(value="/",method = RequestMethod.GET)
	public MainMessage getMessage(){
		return new MainMessage("This is the root controller !!");
	}
}

The last step is to add the @EnableSwagger2 annotation on the Application class.
Now, to view the Swagger UI go to the following url http://localhost:8080/swagger-ui.html#/ (assuming it deployed on your localhost), and to get the docs go to the url http://localhost:8080/v2/api-docs

Note: If you miss this annotation @EnableSwagger2 you get the following error..

Screen Shot 2018-01-23 at 6.26.20 PM

Creating a Spring Boot application on Google App Engine Standard

January 23, 2018 Leave a comment

In our last blog we saw how to create a simple application and deploy in Google App Engine Flex. In this write up we will see how to do the same but in the Google App Engine standard environment.
Last year the Standard App Engine was lagging behind specifically with the java runtime version that was supported but the recent updates have removed that issue. You can read more about the releases here https://cloud.google.com/appengine/docs/standard/java/release-notes

We will follow the steps from our previous blog up to the point where we add the appengine-maven-plugin. We do not use the app.yaml file in the App Engine standard. Instead we use the appengine-web.xml config file. The following steps need to be followed next.

1. Appengine-web.xml file
Create a new source folder : /src/main/webapp/WEB-INF. In this folder add a new file with the name appengine-web.xml. The following is the content of the file.

<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <threadsafe>true</threadsafe>
    <runtime>java8</runtime>
</appengine-web-app>

2. Change the packaging
Change the packaging in the pom file from jar to war

<packaging>war</packaging>

3. Add web.xml file
Add a web.xml file in the WEB-INF folder created aboove. The content of the file is as below.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
</web-app>

4. Add a logging.properties file in the same location with the following config for now

.level = WARNING
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=FINEST
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n

5. Change the main application class.
In our case the Application name is GoogleStandard and the SpringBootApplication class is called GoogleStandardApplication. We will extend the class to implement SpringBootServletInitializer. The class will look like the following

@SpringBootApplication
public class GoogleStandardApplication extends SpringBootServletInitializer{

	@Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(GoogleStandardApplication.class);
    }
	
	public static void main(String[] args) {
		SpringApplication.run(GoogleStandardApplication.class, args);
	}
}

Deploy the application using the following command

mvn appengine:deploy

If a new project was created for the deployment, make sure to use the following commands to use the new project and create the appengine instance.

gcloud config set project PROJECT-NAME
gcloud app create

Creating a Spring Boot application on Google App Engine Flex

January 22, 2018 Leave a comment

Google provides the infrastructure to build web applications on the cloud. There are different ways in which this can be done. App Engine provides a way where the scale up is provided automatically with other features. Within the App Engine offering there are 2 options – Flex and Standard. Flex App Engine allows us to use a Docker container for the application. In addition it is built on Compute Engine VM instances. Differences in Flex and Standard can be found here : https://cloud.google.com/appengine/docs/the-appengine-environments

We will see how to build and deploy a simple Spring Boot application on a Google App Engine Flex environment.
The initial setup of the Google Cloud Platform project and the SDK can be found here. (https://codesilo.wordpress.com/2018/01/18/gcloud-sdk-setup-for-app-engine-java-for-mac/)

We will create a simple Spring Boot application as described in the “Creating the Spring Boot Application” section here (https://codesilo.wordpress.com/2017/12/26/spring-boot-rest-api-integration-with-salesforce/)

After creating the project the billing needs to be enabled for the project –
https://console.developers.google.com/billing
For the free limits, the billing will not be enabled but it will need to be verified anyways

Once the Spring boot application is created we will add the appengine maven plugin to the pom file of the project. The latest version available currently is 1.3.2. It can be found here https://mvnrepository.com/artifact/com.google.cloud.tools/appengine-maven-plugin/1.3.2. The following needs to be added under in the pom.xml file

<!-- https://mvnrepository.com/artifact/com.google.cloud.tools/appengine-maven-plugin -->
<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>appengine-maven-plugin</artifactId>
    <version>1.3.2</version>
</plugin>

There is another plugin that is available with the group id com.google.appengine. But this is the older plugin available for the sdk. Additional information of the difference in the 2 plugins can be found here: https://stackoverflow.com/questions/40627278/which-app-engine-maven-plugin-to-use

In the next step, create a folder under the “main” folder with the name “appengine”. We will create a file app.yaml in this folder with the following contents for now. Details on the app.yaml configuration can be found here https://cloud.google.com/appengine/docs/flexible/java/configuring-your-app-with-app-yaml

# [START appyaml]
runtime: java
env: flex
runtime_config:
   jdk: openjdk8
manual_scaling:
  instances: 1
handlers:
- url: /.*
  script: this field is required, but ignored
# [END appyaml]

In order to run the application locally, use the following command

mvn spring-boot:run

To deploy the application to the App Engine on Google Cloud use the following command

mvn appengine:deploy

Once the deployment is successful, you should be able to goto the cloud console at https://console.cloud.google.com/ and see the services deployed on the app engine instance (you can see this on the app engine dashboard). In our configuration above, it is going to be a default service. Clicking on the service will take you to the root context of our deployed application.

Screen Shot 2018-01-22 at 1.56.24 PM

Screen Shot 2018-01-22 at 1.57.08 PM

GCloud SDK Setup for App Engine Java for Mac

January 18, 2018 Leave a comment

Google Cloud Platform (https://cloud.google.com/) is a cloud infrastructure solution from Google. In the next blog we will build and deploy a simple Spring Boot application on Google Flex App Engine. This writeup covers the setup of the gcloud sdk on Mac OS X for the purpose above.

First check if Python is installed in the OS using one of the following commands

python -V
python --version

 

Download the Google Cloud SDK from this location for your OS https://cloud.google.com/sdk/ .In my case I downloaded the 64bit version for Mac: google-cloud-sdk-157.0.0-darwin-x86_64.tar.gz

Extract the files and then run the command ./install.sh in the directory created. This will install the sdk.
Once installed the output will show the components installed and give some commands that can be run to update the components.

┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                  Components                                                 │
├───────────────┬──────────────────────────────────────────────────────┬──────────────────────────┬───────────┤
│     Status    │                         Name                         │            ID            │    Size   │
├───────────────┼──────────────────────────────────────────────────────┼──────────────────────────┼───────────┤
│ Not Installed │ App Engine Go Extensions                             │ app-engine-go            │  96.7 MiB │
│ Not Installed │ Cloud Bigtable Command Line Tool                     │ cbt                      │   4.0 MiB │
│ Not Installed │ Cloud Bigtable Emulator                              │ bigtable                 │   3.3 MiB │
│ Not Installed │ Cloud Datalab Command Line Tool                      │ datalab                  │   < 1 MiB │
│ Not Installed │ Cloud Datastore Emulator                             │ cloud-datastore-emulator │  15.4 MiB │
│ Not Installed │ Cloud Datastore Emulator (Legacy)                    │ gcd-emulator             │  38.1 MiB │
│ Not Installed │ Cloud Pub/Sub Emulator                               │ pubsub-emulator          │  21.0 MiB │
│ Not Installed │ Emulator Reverse Proxy                               │ emulator-reverse-proxy   │  14.5 MiB │
│ Not Installed │ Google Container Registry's Docker credential helper │ docker-credential-gcr    │   2.3 MiB │
│ Not Installed │ gcloud Alpha Commands                                │ alpha                    │   < 1 MiB │
│ Not Installed │ gcloud Beta Commands                                 │ beta                     │   < 1 MiB │
│ Not Installed │ gcloud app Java Extensions                           │ app-engine-java          │ 132.2 MiB │
│ Not Installed │ gcloud app PHP Extensions (Mac OS X)                 │ app-engine-php-darwin    │  21.9 MiB │
│ Not Installed │ gcloud app Python Extensions                         │ app-engine-python        │   6.4 MiB │
│ Not Installed │ kubectl                                              │ kubectl                  │  14.8 MiB │
│ Installed     │ BigQuery Command Line Tool                           │ bq                       │   < 1 MiB │
│ Installed     │ Cloud SDK Core Libraries                             │ core                     │   6.1 MiB │
│ Installed     │ Cloud Storage Command Line Tool                      │ gsutil                   │   2.9 MiB │
│ Installed     │ Default set of gcloud commands                       │ gcloud                   │           │
└───────────────┴──────────────────────────────────────────────────────┴──────────────────────────┴───────────┘

 
Based on the input provided by you, the bash_profile will be updated and the PATH will be set for gcloud.
Once this is done run the following command

gcloud init

This will prompt you to login to your account. This along with some more input will provide the initial setup for the components. Once done the process will also give an option to use an existing project on Google Cloud, or , create a new one. Select a new project and proceed.

In order to develop a Java application on Google App Engine we require the app engine components. We will install those components now by running the following command.

gcloud components install app-engine-java

This should complete the setup for the sdk. The rest of the information/steps is optional.

Eclipse Plugin for Development
I tried to use the Eclipse plugin but was outdated at the time and works only for the standard App Engine (not for the Flex app engine). Have been using the command line for the push since then.

Misc Commands
Here are some other commands that might be useful in future development.

Used to set a Project for the SDK

gcloud config set project PROJECT_NAME 

List of configuration parameters that have been set(includes account, usage reporting flag and project name)

gcloud config list

List of authorized accounts and the one being used actively

gcloud auth list

Used to update the components in the sdk that was installed.

gcloud components update

Reference: https://cloud.google.com/sdk/

Multiple versions of Java on Mac OS X

January 18, 2018 Leave a comment

I was working on the Google Cloud platform a couple of months back when Java 8 was not supported on the Standard App Engine. However, the Flex engine had Java 8 support (currently even the standard has support for it). When trying out Standard Vs Flex, I had to find something to switch between the Java versions. Jenv came to the rescue (http://www.jenv.be/)

The following steps show how to work with jenv on Mac OS X. (Java 8 was already installed on my OS.)

Use Homebrew to install some packages. Brew is the command for Homebrew (https://brew.sh/) and Homebrew is the package manager for Mac.

Update Homebrew and install Cask if it is not installed already(Cask is an extension of brew for graphical applications)

brew update && brew cask 

Update the repositories in brew to use caskroom/versions

brew tap caskroom/versions

Install Java7 from Caskroom versions

brew cask install java7

Use brew to install jenv

brew install jenv

The following command gives the list of versions of Java installed. The one with the * is the one being used globally

jenv versions 

To install a new version, first we need to make the versions dir

mkdir /Users/codesilo/.jenv/versions

Then add the Java (installed by cask earlier) to the versions of jenv by using the following command

jenv add /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/
jenv add /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/

Use the following command to set the selected install as the global jenv version to be used

jenv global oracle64-1.7.0.80

The versions command will give the following result now

$ jenv versions
  system
  1.7
  1.7.0.80
  1.8
  1.8.0.101
* oracle64-1.7.0.80 (set by /Users/codesilo/.jenv/version)
  oracle64-1.8.0.101

You can also configure directory level version (local version) or shell instance version by using the following commands

$ jenv local oracle64-1.7.0.80
$ jenv shell oracle64-1.7.0.80

 

Categories: Java, Shell Tags: , , , , , , ,

Salesforce REST API Composite Services

January 17, 2018 Leave a comment

Salesforce has a feature on the REST API called as the Composite Resources. This allows us to build API requests by combining more than one request. Shown below are some  examples of them with Postman (https://www.getpostman.com/). Documentation of Composite Resources can be found here (https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite.htm)

The first step is to get the authorization token.

Endpoint : https://login.salesforce.com/services/oauth2/token
Method Type : POST

The body should have form data which has the following : grant_type, username, password, client_id, client_secret. client_id and client_secret are taken from the App created in Salesforce for the integration. See details in the “Setting up Salesforce Connected App” section on this blog post here : https://codesilo.wordpress.com/2017/12/26/spring-boot-rest-api-integration-with-salesforce/
Username and Password should be the username and password of the account being used for the API access. Security Token, if enabled, should be concatenated to the password above. API access should be provided to the Profile of this account.
The grant_type is “password”. It is the word password without the quotes and not the actual password of the user account.

Screen Shot 2018-01-17 at 12.53.33 PM
On making the call, we should get the access_token in the response. This access_token is now used in all subsequent calls below. The instance_url from the response is used to create the endpoint in the requests below.

For all calls below we will have the following 2 headers

Authorization : Bearer **accesss_token HERE**
Content-Type : application/json

Note: For the authorization header the value has to be “Bearer” without the quotes followed by a space and the access_token

1. Composite API Call:

Endpoint: https://INSTANCE_URL_HERE/services/data/v41.0/composite/
Method Type: POST

Sample Body:

{
    "allOrNone" : true,
    "compositeRequest" : [{
        "method" : "GET",
        "url" : "/services/data/v38.0/query?q=select id from Account where id='0014600000KPgY8'",
        "referenceId" : "AccountIden"
        },{
        "method" : "GET",
        "url" : "/services/data/v38.0/query?q=select name from Account where id = '@{AccountIden.records[0].Id}'",
        "referenceId" : "TestDataQuery"
    	}
    ]
}

Screen Shot 2018-01-17 at 1.01.38 PM

2. Batch API call:

Endpoint: https://INSTANCE_URL_HERE/services/data/v40.0/composite/batch/
Method Type: POST

Sample Body:

{
    "batchRequests" : [{
        "method" : "GET",
        "url" : "/services/data/v38.0/query?q=select id from Account where id='0014600000KPgY8'"
        },{
        "method" : "GET",
        "url" : "/services/data/v38.0/query?q=select id, name from SalesforceTestData__c where id = 'a0E46000003icvo'"
    	}
    ]
}

Screen Shot 2018-01-17 at 1.02.50 PM

3. SObject Tree API call:
Endpoint: services/data/v40.0/composite/tree/Account
Method Type: POST

Sample Body:

{
"records" :[{
    "attributes" : {"type" : "Account", "referenceId" : "ref1"},
    "name" : "SampleAccount",
    "phone" : "1234567890",
    "website" : "www.salesforce.com",
    "numberOfEmployees" : "100",
    "industry" : "Banking",
    "Contacts" : {
      "records" : [{
         "attributes" : {"type" : "Contact", "referenceId" : "ref2"},
         "lastname" : "Smith",
         "title" : "President",
         "email" : "sample@salesforce.com"
         },{
         "attributes" : {"type" : "Contact", "referenceId" : "ref3"},
         "lastname" : "Evans",
         "title" : "Vice President",
         "email" : "sample@salesforce.com"
         }]
      }
    },{
    "attributes" : {"type" : "Account", "referenceId" : "ref4"},
    "name" : "SampleAccount2",
    "phone" : "1234567890",
    "website" : "www.salesforce2.com",
    "numberOfEmployees" : "100",
    "industry" : "Banking"
     }]
}

Note: In this example we are adding Contacts tp an account and adding another account without a Contact (Reference: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/requests_composite_sobject_tree.htm#topic-title)

4. SObject Collections:
This is similar to the Tree API call however it does not take child records in the request. This can be used to execute requests on several records at once. It can also be used to retrieve the data (which is different in comparison to tree)
Examples are described well here: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobjects_collections.htm