The Streaming API can provide near real time updates on changes to any object data in an organisation. In this session we will demonstrate and share code for a custom Process Orchestration solution powered by the Streaming API and Heroku. Using this solution users can define criteria which the platform will apply to monitor records created or updated by users in real time. The session will demonstrate an example process using REST services taken from those offered by many of todays leading REST API providers including Salesforce.
2. Safe harbor
Safe harbor statement under the Private Securities Litigation Reform Act of 1995:
This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties
materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results expressed or
implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking,
including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements
regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded
services or technology developments and customer contracts or use of our services.
The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality
for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results
and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of intellectual property and other
litigation, risks associated with possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating
history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful
customer deployment, our limited history reselling non-salesforce.com products, and utilization and selling to larger enterprise customers.
Further information on potential factors that could affect the financial results of salesforce.com, inc. is included in our annual report on Form 10-
Q for the most recent fiscal quarter ended July 31, 2012. This documents and others containing important disclosures are available on the SEC
Filings section of the Investor Information section of our Web site.
Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available
and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features
that are currently available. Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements.
3. All about FinancialForce.com
Leading Native ISV on Force.com
• #1 Accounting App on Force.com
• #1 Professional Services Automation App on Force.com
Backed by Salesforce.com & UNIT4
• UNIT4 - $600 million, 33 years building business apps
Growing Rapidly
• San Francisco HQ – 595 Market St.
• 145 Employees
• Customers in 23 countries
5. Building declarative applications
Building applications that support Force.com declarative features is critical.
Building applications that consume Force.com declarative features to support your
app is smart!
Encourage users admins to use what they have already
• Building their solution consisting of part your app and part platform features can be a powerful combo!
Make sure you understand how to be dynamic…
• Metadata API, Apex Describe
• Dynamic SOQL, Dynamic Visualforce, Fieldsets
• Reference Formulas, Rollup Summaries, List Views etc..
Make your applications metadata portable between orgs!
Consultants love our declarative applications!
6. Our user story
As A: System Applications Administrator
I want: To be able to link a series of HTTP / REST based API’s to data changes
occurring in my organization, based on criteria I provide. Without resorting
to Apex coding.
So that: I can harness the power of many HTTP / REST based services to
further automate my companies business processes. And leverage my skills
as a System Administrator.
7. Our user case : eBay Integration
Invoice Number: INV-000001
Process Manager
eBay Item: 12345678
Streaming API via
Bayeux Protocol
Overview Salesforce Streaming API via Bayeux Protocol
1. Create a Push Topic in Force.com
• Using the SOQL statement you provide. Salesforce servers will send JSON
messages to listeners. When changes occur to record data that meet the
criteria expressed by the SOQL query.Item: 12345678
eBay
2. Install a Bayeux Client Library
• Clients are available from http://cometd.org (none for Apex)
3. Connect and start Listening!
• This sample uses the Java Bayeux library
9. PushTopic (Streaming API) Name Query Select From
Name processchanges Process__c
Query
NotifyForOperations processlogslinescreated ProcessLogLine__c
NotifyForFields {Proccess__c.Id} {SourceObject__c}
Process__c /topic/processchanges Process__c Listener
Name On Insert
SourceObject__c ProcessStep__c
1. Generate PushTopic Query
When__c Name
2. Create new PushTopic record
ListViewName__c HTMLEndpoint__c
3. Subscribe via Streaming API
HTMLHeader__c
4. Start {SourceObject__c} Listener
HTMLMethod__c Admin creates
5. Log to ProcessLogLineItem__c
HTMLBody__c Process
ProcessLogLine__c Listener ProcessLogLine__c
Live Log Viewer Message__c {SourceObject__c} Listener
“1. xxxx
2. xxxx”
/topic/processlogslinescreated
On Update
1. Read ProcessSteps__c
Invoice__c /topic/{Process__c.Id} 2. Read {SourceObject__c}
Name 3. Call {HTMLEndpoint__c} …
Account__c 4. Log to ProcessLogLineItem__c
Amount__c
eBayItemNumber__c
eBayCompleteSaleRequest__c
Invoice Clerk
updates Invoice HTTP POST:
<CompleteSaleRequest>
<Item>{!eBayItemNumber}</Item>
</CompleteSaleRequest>
10. Java Code : Heroku Worker Process
/**
* Heroku Work Processes are simply Java classes with a main method!
*/
01: public class WorkerProcess
02: {
03: // Salesforce API's
04: private static final String SALESFORCE_API = "25.0";
05: private static final String LOGIN_ENDPOINT = "https://login.salesforce.com/services/Soap/u/" + SALESFORCE_API;
06: private static final String REST_ENDPOINT_URI = "/services/data/v" + SALESFORCE_API + "/";
07: private static final String STREAMING_ENDPOINT_URI = "/cometd/" + SALESFORCE_API;
08:
09: /**
10: * Main entry point for the Heroku Worker Process
11: * @param args
12: * @throws Exception
13: */
14: public static void main(String[] args) throws Exception
15: {
16: // Startup and assignment of listeners to Salesforce Streaming API topics…
17: }
11. Java Code : Process__c Listener
/**
* Listener : Process__c
* Comment : Bayeux Client used connect to Streaming API and implement MessageListener interface
* @see makeStreamingAPIConnection method
* @see JSON class from Jetty
* @see processInsert method
*/
01: // Connect to Salesforce Streaming API
02: final BayeuxClient client = makeStreamingAPIConnection(loginResult);
03:
04: // Subscribe to the Process__c topic to listen for new or updated Processes
05: client.getChannel("/topic/processchanges").subscribe(new MessageListener()
06: {
07: public void onMessage(ClientSessionChannel channel, Message message)
08: {
09: HashMap<String, Object> data = (HashMap<String, Object>) JSON.parse(message.toString());
10: HashMap<String, Object> record = (HashMap<String, Object>) data.get("data");
11: HashMap<String, Object> sobject = (HashMap<String, Object>) record.get("sobject");
12: HashMap<String, Object> event = (HashMap<String, Object>) record.get("event");
13: // Event type, insert or update?
14: String type = (String) event.get("type");
15: if(type.equals("created"))
16: processInsert(loginResult, client, sobject);
17: }
18: });
12. Java Code : Process__c Listener
Java Code : Process__c Listener
/**
* Listener : Process__c
* Step : Connect and Listen
* API connections and message data, used WSC manual login to obtain Metadata API base URL from
* LoginResult (not normally exposed by the standard connector classes)
* @see makeRestConnection method (Rest URL helper)
*/
01: // Connection configuration
02: ConnectorConfig metadataConfig = new ConnectorConfig();
03: metadataConfig.setSessionId(loginResult.getSessionId());
04: metadataConfig.setServiceEndpoint(loginResult.getMetadataServerUrl());
05: MetadataConnection metadataConnection =
06: com.sforce.soap.metadata.Connector.newConnection(metadataConfig);
07:
08: // Make a REST connection
09: RestConnection restConnection = makeRestConnection(loginResult);
10:
11: // Message data from Proccess__c
12: String processId = (String) sObject.get("Id");
13: String processName = (String) sObject.get("Name");
14: String sourceObject = (String) sObject.get("SourceObject__c");
15: String listViewName = (String) sObject.get("ListViewName__c");
13. Java Code : Process__c Listener
Java Code : Process__c Listener
/**
* Listener : Process__c
* Step : Generate PushTopic Query
* Comment : Retrieve via Metadata API custom object definition to get at the List View
* definition for generating PushTopic Query
*/
01: // Retrieve Custom Object Meta data (and thus List View definition) for Source Object
02: RetrieveRequest retrieveRequest = new RetrieveRequest();
03: retrieveRequest.setSinglePackage(true);
04: com.sforce.soap.metadata.Package packageManifest = new com.sforce.soap.metadata.Package();
05: ArrayList<PackageTypeMembers> types = new ArrayList<PackageTypeMembers>();
06: PackageTypeMembers packageTypeMember = new PackageTypeMembers();
07: packageTypeMember.setName("CustomObject");
08: packageTypeMember.setMembers(new String[] { sourceObject });
09: types.add(packageTypeMember);
10: packageManifest.setTypes((PackageTypeMembers[]) types.toArray(new PackageTypeMembers[] {}));
11: retrieveRequest.setUnpackaged(packageManifest);
12: AsyncResult response = metadataConnection.retrieve(retrieveRequest);
13: while(!response.isDone())
14: {
15: Thread.sleep(1000);
16: response = metadataConnection.checkStatus(new String[] { response.getId()} )[0];
17: }
18: RetrieveResult retrieveResult = metadataConnection.checkRetrieveStatus(response.getId());
14. Java Code : Process__c Listener
Java Code : Process__c Listener
/**
* Listener : Process__c
* Step : Generate PushTopic Query
* Comment : Uses the WSC TypeMapper to deserialise the Custom Object definition from the
* package zip returned by „retrieve‟
*/
01: // Parse Custom Object metadata for Process Source Object
02: CustomObject customObject = new CustomObject();
03: byte[] zipBytes = retrieveResult.getZipFile();
04: ZipInputStream zipis = new ZipInputStream(new ByteArrayInputStream(zipBytes, 0, zipBytes.length));
05: ZipEntry zipEntry = null;
06: while((zipEntry = zipis.getNextEntry()) != null) {
07: if(zipEntry.getName().endsWith(sourceObject + ".object")) / Process Source Object?
08: {
09: TypeMapper typeMapper = new TypeMapper();
10: XmlInputStream xmlis = new XmlInputStream();
11: xmlis.setInput(zipis, "UTF-8");
12: customObject.load(xmlis, typeMapper);
13: zipis.closeEntry();
14: break;
15: }
16: }
17: // Find the List View indicated on the Process to define query for Streaming API Push Topic
18: ListView processlistView = null;
19: for(ListView listView : customObject.getListViews()) { ... }
15. Java Code : Process__c Listener
Java Code : Process__c Listener
/**
* Listener : Process__c
* Step : Generate PushTopic Query
* Comment : Parse List View definition to construct SOQL Query for Push Topic. Code shortened
* for this slide.
*/
01: // Generate SOQL statement for PushTopic based on List View definition
02: StringBuilder fieldList = new StringBuilder("Id");
03: for(String field : processlistView.getColumns())
04: fieldList.append(", " + field);
05: // Simple version assumes AND‟s, ignores processListView.getBooleanFilter()
06: StringBuilder whereClause = new StringBuilder();
07: for(ListViewFilter lvFilter : processlistView.getFilters())
08: {
09: switch (lvFilter.getOperation())
10: {
11: case equals:
12: // ...
13: }
14: }
15: // Construct SOQL query statement
16: StringBuilder soql = new StringBuilder()
17: .append("select ”) .append(fieldList.toString())
18: .append("from ”) .append(sourceObject + " ")
19: .append("where ”) .append(whereClause.toString());
16. Java Code : Process__c Listener
Java Code : Process__c Listener
/**
* Listener : Process__c
* Step : Create PushTopic and subscribe
* Comment : PushTopic Name has to be unique thus we used the Process__c.Id.
* It is possible to immediately subscribe once the Push Topic has been created!
* @see SourceObjectListener class implements MessageListener interface!
*/
01: // Create PushTopic
02: PushTopic pushTopic = new PushTopic();
03: pushTopic.Name = processId;
04: pushTopic.Query = soql.toString();
05: pushTopic.ApiVersion = SALESFORCE_API;
06: pushTopic.NotifyForOperations = "All"; // TODO: Interpret the 'When__c' field from Process__c
07: pushTopic.NotifyForFields = "Referenced";
08: restConnection.create(pushTopic);
09:
10: // Update Process with Push Topic Name (so that if the worker restarts it can reconnect)
11: Process__c process = new Process__c();
12: process.PushTopicName__c = pushTopic.Name;
13: restConnection.update(process, processId);
14:
15: // Start listening on the new Push Topic immediately!
16: client.getChannel("/topic/"+pushTopic.Name).subscribe(
17: new SourceObjectListener(loginResult, processId, sourceObject));
17. Java Code {SourceObject__c} Listener
Java Code : : {SourceObject__c} Listener
/**
* Listener : {SourceObject__c}
* Step 1 : Parse Source Object Id from message and read current process steps. Determine what HTTP call
* information will be provided by the source object record fields.
*/
01: public static class SourceObjectListener implements MessageListener
02: {
03: public void onMessage(ClientSessionChannel channel, Message message)
04: {
05: // Source Object Message
06: HashMap<String, Object> data = (HashMap<String, Object>) JSON.parse(message.toString());
07: HashMap<String, Object> record = (HashMap<String, Object>) data.get("data");
08: HashMap<String, Object> sobject = (HashMap<String, Object>) record.get("sobject");
09: String sourceId = (String) sobject.get("Id")
10: // Query Process Steps
11: QueryResult processStepsResult = m_partnerConnection.query(
12: “select Id, Name, " +
13: "HTTPEndPoint__c, HTTPEndPointFrom__c, " +
14: "HTTPMethod__c, HTTPMethodFrom__c, " +
15: "HTTPHeader__c, HTTPHeaderFrom__c, " +
16: "HTTPBody__c, HTTPBodyFrom__c " +
17: ”from ProcessStep__c " +
18: "where Process__c = '" + m_processId + "'");
19. Java Code {SourceObject__c} Listener
Java Code : : {SourceObject__c} Listener
/**
* Listener : {SourceObject__c}
* Step 3 : Use Jetty HTTP Client to make callout. HTTP call out details are taken from literals on the step
* or from formula fields on the source record
*/
01: // Create the HTTP client
02: HttpClient httpClient = new HttpClient();
03: httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
04: httpClient.start();
05: // HTTP parameters
06: String httpEndpoint = ...
07: String httpHeader = ...
08: String httpMethod = ...
09: String httpBody = ...
10: processStep.HTTPBodyFrom__c.equals("Literal") ?
11: processStep.HTTPBody__c : (String) sourceRecord.getField(processStep.HTTPBody__c);
12: // Construct HTTP request
13: ContentExchange contentExchange = new ContentExchange(true);
14: contentExchange.setMethod(httpMethod);
15: contentExchange.setURL(httpEndpoint);
16: contentExchange.setRequestHeader(...);
17: contentExchange.setRequestContent(new ByteArrayBuffer(httpBody));
18: httpClient.send(contentExchange);
19: contentExchange.waitForDone();
20. Resources
Java Code : {SourceObject__c} Listener
• Other // TODO:’s and Ideas?!
– Record Types for Process Steps?
– State / Variables between Steps?
• Source Code and Contact Details
GitHub:
https://github.com/financialforcedev
Heroku App (Clone):
https://api.heroku.com/myapps/df12-processmanager/clone
Twitter: andyinthecloud
Notes de l'éditeur
Rehab Breakfast on Friday, talking to people in the area for roles we have open, come along even if your tired! ;-)Expanding Development Group, adding Development Team in SFIndustrial Strength Applications
- Mention platform Flow and Workflow differences and capabilities vs this solution.