You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2017/07/19 12:29:02 UTC
[1/2] incubator-unomi git commit: UNOMI-116 Salesforce CRM Connector
- Initial commit
Repository: incubator-unomi
Updated Branches:
refs/heads/master 139e1b751 -> a897ab843
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImpl.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImpl.java b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImpl.java
new file mode 100644
index 0000000..7ef6bea
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImpl.java
@@ -0,0 +1,815 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.unomi.sfdc.services.internal;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.apache.unomi.sfdc.services.SFDCConfiguration;
+import org.apache.unomi.sfdc.services.SFDCService;
+import org.apache.unomi.sfdc.services.SFDCSession;
+import org.cometd.bayeux.Channel;
+import org.cometd.bayeux.Message;
+import org.cometd.bayeux.client.ClientSessionChannel;
+import org.cometd.client.BayeuxClient;
+import org.cometd.client.transport.ClientTransport;
+import org.cometd.client.transport.LongPollingTransport;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.util.ajax.JSON;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.*;
+
+/**
+ * Implementation of the Salesforce connector interface
+ */
+public class SFDCServiceImpl implements SFDCService {
+ private static final Logger logger = LoggerFactory.getLogger(SFDCServiceImpl.class.getName());
+
+ private static final String REST_ENDPOINT_URI = "/services/data/v38.0";
+ private static final String STREAMING_ENDPOINT_URI = "/cometd/38.0";
+
+ private static final int CONNECTION_TIMEOUT = 20 * 1000; // milliseconds
+ private static final int READ_TIMEOUT = 120 * 1000; // milliseconds
+
+ private SFDCConfiguration sfdcConfiguration;
+ private SFDCConfiguration defaultSFDCConfiguration;
+
+ private Set<String> sfdcLeadMandatoryFields = new TreeSet<>();
+ private Set<String> sfdcLeadUpdateableFields = new TreeSet<>();
+
+ private SFDCSession sfdcSession;
+ private DateFormat iso8601DateFormat = new ISO8601DateFormat();
+
+ private PersistenceService persistenceService;
+
+ public void setPersistenceService(PersistenceService persistenceService) {
+ this.persistenceService = persistenceService;
+ }
+
+ public void setDefaultSFDCConfiguration(SFDCConfiguration defaultSFDCConfiguration) {
+ this.defaultSFDCConfiguration = defaultSFDCConfiguration;
+ }
+
+ public SFDCSession getSFDCSession() {
+ return sfdcSession;
+ }
+
+ @Override
+ public SFDCConfiguration loadConfiguration() {
+ if (persistenceService == null) {
+ return null;
+ }
+ SFDCConfiguration sfdcConfiguration = persistenceService.load("sfdcConfiguration", SFDCConfiguration.class);
+ return sfdcConfiguration;
+ }
+
+ @Override
+ public boolean saveConfiguration(SFDCConfiguration sfdcConfiguration) {
+ if (persistenceService == null) {
+ return false;
+ }
+ boolean result = persistenceService.save(sfdcConfiguration);
+ if (result) {
+ this.sfdcConfiguration = sfdcConfiguration;
+ try {
+ if (login(sfdcConfiguration)) {
+ return true;
+ }
+ } catch (HttpException e) {
+ logger.warn("Error trying to login with new configuration {}", sfdcConfiguration, e);
+ result = false;
+ } catch (IOException e) {
+ logger.warn("Error trying to login with new configuration {}", sfdcConfiguration, e);
+ result = false;
+ }
+ } else {
+ logger.error("Error trying to save new Salesforce connection configuration !");
+ }
+ return result;
+ }
+
+ public void start() {
+ try {
+ iso8601DateFormat = new ISO8601DateFormat();
+
+ SFDCConfiguration sfdcConfiguration = loadConfiguration();
+ if (sfdcConfiguration != null) {
+ this.sfdcConfiguration = sfdcConfiguration;
+ } else {
+ this.sfdcConfiguration = defaultSFDCConfiguration;
+ }
+
+ if (this.sfdcConfiguration.isComplete()) {
+ boolean loginSuccessful = login(this.sfdcConfiguration);
+ if (!loginSuccessful) {
+ throw new Exception("Login failed");
+ }
+ sfdcLeadMandatoryFields = getLeadMandatoryFields();
+ // setupPushTopics(SFDCSession.getEndPoint(), SFDCSession.getSessionId());
+ logger.info("Salesforce connector initialized successfully.");
+ } else {
+ logger.warn("Salesforce connector is not yet configured.");
+ }
+ } catch (HttpException | IOException e) {
+ logger.error("Failed to init SFDCService properly", e);
+ } catch (Exception e) {
+ logger.error("Failed to init SFDCService properly", e);
+ }
+ }
+
+ public void stop() {
+ }
+
+ public Set<String> getRecentLeadIds() {
+ if (!isConnected()) {
+ return null;
+ }
+ Set<String> recentLeadIds = new LinkedHashSet<>();
+
+ String baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/sobjects/Lead";
+ HttpGet getRecentLeads = new HttpGet(baseUrl);
+
+ try {
+ Object responseObject = handleRequest(getRecentLeads);
+ if (responseObject == null) {
+ logger.warn("Couldn't retrieve recent leads");
+ return null;
+ }
+ Map<String, Object> queryResponse = (Map<String, Object>) responseObject;
+ if (queryResponse.containsKey("recentItems")) {
+ logger.debug("Response received from Salesforce: {}", queryResponse);
+ Object[] recentItems = (Object[]) queryResponse.get("recentItems");
+ for (Object recentItem : recentItems) {
+ Map<String, String> recentItemMap = (Map<String, String>) recentItem;
+ recentLeadIds.add(recentItemMap.get("Id"));
+ }
+ }
+
+ } catch (IOException e) {
+ logger.error("Error getting recent leads", e);
+ } catch (HttpException e) {
+ logger.error("Error getting recent leads", e);
+ }
+
+ return recentLeadIds;
+ }
+
+ public Map<String,Object> getSObject(String sobjectName, String objectId) {
+ if (!isConnected()) {
+ return null;
+ }
+ Map<String, Object> sobjectMap = new LinkedHashMap<>();
+
+ String baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/sobjects/" + sobjectName +"/" + objectId;
+ HttpGet getSObject = new HttpGet(baseUrl);
+
+ try {
+ Object responseObject = handleRequest(getSObject);
+ if (responseObject == null) {
+ logger.warn("Couldn't retrieve sobject {} with id {}", sobjectName, objectId);
+ return null;
+ }
+ Map<String, Object> queryResponse = (Map<String, Object>) responseObject;
+ if (queryResponse != null) {
+ logger.debug("Response received from Salesforce: {}", queryResponse);
+ sobjectMap = new LinkedHashMap<>(queryResponse);
+ }
+
+ } catch (IOException e) {
+ logger.error("Error getting sobject {} with id {}", sobjectName, objectId, e);
+ } catch (HttpException e) {
+ logger.error("Error getting sobject {} with id {}", sobjectName, objectId, e);
+ }
+ return sobjectMap;
+ }
+
+ public Map<String,Object> getSObjectDescribe(String sobjectName) {
+ Map<String, Object> sobjectDescribe = new LinkedHashMap<>();
+ if (!isConnected()) {
+ return null;
+ }
+
+ String baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/sobjects/" + sobjectName +"/describe";
+ HttpGet getSObjectDescribe = new HttpGet(baseUrl);
+
+ try {
+ Object responseObject = handleRequest(getSObjectDescribe);
+ if (responseObject == null) {
+ logger.warn("Couldn't retrieve sobject {} describe", sobjectName);
+ return null;
+ }
+ Map<String, Object> queryResponse = (Map<String, Object>) responseObject;
+ if (queryResponse != null) {
+ logger.debug("Response received from Salesforce: {}", queryResponse);
+ sobjectDescribe = new LinkedHashMap<>(queryResponse);
+ }
+
+ } catch (IOException e) {
+ logger.error("Error getting sobject {}", sobjectName, e);
+ } catch (HttpException e) {
+ logger.error("Error getting sobject {}", sobjectName, e);
+ }
+ return sobjectDescribe;
+ }
+
+ public Map<String, Object> getLead(String leadId) {
+ return getSObject("Lead", leadId);
+ }
+
+ public Set<String> getLeadMandatoryFields() {
+ Set<String> mandatoryFields = new TreeSet<>();
+ if (!isConnected()) {
+ return null;
+ }
+ Map<String,Object> leadDescribe = getSObjectDescribe("Lead");
+ Object[] fields = (Object[]) leadDescribe.get("fields");
+ Set<String> updateableFields = new TreeSet<>();
+ Set<String> compoundFieldNames = new TreeSet<>();
+ for (Object field : fields) {
+ Map<String,Object> fieldDescribe = (Map<String,Object>) field;
+ String fieldName = (String) fieldDescribe.get("name");
+ String compoundFieldName = (String) fieldDescribe.get("compoundFieldName");
+ if (compoundFieldName != null) {
+ compoundFieldNames.add(compoundFieldName);
+ }
+ String fieldType = (String) fieldDescribe.get("type");
+ Boolean fieldUpdateable = (Boolean) fieldDescribe.get("updateable");
+ Boolean fieldCreateable = (Boolean) fieldDescribe.get("createable");
+ Boolean fieldDefaultedOnCreate = (Boolean) fieldDescribe.get("defaultedOnCreate");
+ Boolean fieldNillable = (Boolean) fieldDescribe.get("nillable");
+ if (fieldUpdateable) {
+ updateableFields.add(fieldName);
+ }
+ if (!fieldNillable && !fieldDefaultedOnCreate) {
+ mandatoryFields.add(fieldName);
+ }
+ }
+ mandatoryFields.removeAll(compoundFieldNames);
+ updateableFields.removeAll(compoundFieldNames);
+ sfdcLeadUpdateableFields = updateableFields;
+ return mandatoryFields;
+ }
+
+ public boolean deleteLead(String leadId) {
+ if (!isConnected()) {
+ return false;
+ }
+ String baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/sobjects/Lead/" + leadId;
+ HttpDelete deleteLead = new HttpDelete(baseUrl);
+ try {
+ Object responseObject = handleRequest(deleteLead);
+ } catch (IOException e) {
+ logger.error("Error deleting lead {}", leadId, e);
+ } catch (HttpException e) {
+ logger.error("Error deleting lead {}", leadId, e);
+ }
+ return true;
+ }
+
+ public Set<String> findLeadIdsByIdentifierValue(String identifierFieldValue) {
+ Set<String> results = new LinkedHashSet<String>();
+ if (!isConnected()) {
+ return results;
+ }
+ Object response = query("SELECT Id FROM Lead WHERE " + sfdcConfiguration.getSfdcIdentifierField() + "='" + identifierFieldValue + "'");
+ if (response == null) {
+ return results;
+ }
+ Map<String, Object> result = (Map<String, Object>) response;
+ Long totalSize = (Long) result.get("totalSize");
+ Boolean done = (Boolean) result.get("done");
+ Object[] recordObjects = (Object[]) result.get("records");
+ if (totalSize == null || totalSize < 1) {
+ return results;
+ }
+ for (Object recordObject : recordObjects) {
+ Map<String, Object> record = (Map<String, Object>) recordObject;
+ if (record.containsKey("Id")) {
+ results.add((String) record.get("Id"));
+ }
+ }
+ return results;
+ }
+
+ @Override
+ public String createOrUpdateLead(Profile profile) {
+ if (!isConnected()) {
+ return null;
+ }
+ // first we must check if an existing lead exists for the profile.
+ String unomiIdentifierValue = (String) profile.getProperty(sfdcConfiguration.getUnomiIdentifierField());
+ logger.info("Checking if we have a lead for identifier value {}...", unomiIdentifierValue);
+ Set<String> foundExistingSfdcLeadIds = findLeadIdsByIdentifierValue(unomiIdentifierValue);
+
+ Map<String, Object> sfdcLeadFields = new HashMap<>();
+ Map<String, Object> existingSfdcLeadFields = new HashMap<>();
+ Date sfdcLastModified = null;
+
+ if (foundExistingSfdcLeadIds.size() > 1) {
+ // we found multiple leads matching the identifier value !
+ logger.warn("Found multiple matching leads for identifier value {}, will use first matching one !", unomiIdentifierValue);
+ }
+
+ if (foundExistingSfdcLeadIds.size() > 0) {
+ logger.info("Found an existing lead, attempting to update it...");
+ // we found an existing lead we must update it
+ existingSfdcLeadFields = getLead(foundExistingSfdcLeadIds.iterator().next());
+ if (existingSfdcLeadFields.get("LastModifiedDate") != null) {
+ try {
+ sfdcLastModified = iso8601DateFormat.parse((String) existingSfdcLeadFields.get("LastModifiedDate"));
+ } catch (ParseException e) {
+ logger.error("Error parsing date {}", existingSfdcLeadFields.get("LastModifiedDate"), e);
+ }
+ }
+ } else {
+ logger.info("No existing lead found.");
+ }
+
+ for (String profilePropertyKey : profile.getProperties().keySet()) {
+ String sfdcFieldName = sfdcConfiguration.getUnomiToSfdcFieldMappings().get(profilePropertyKey);
+ if (sfdcFieldName == null) {
+ // we skip unmapped fields
+ continue;
+ }
+ Object unomiPropertyValue = profile.getProperties().get(profilePropertyKey);
+ if (existingSfdcLeadFields.get(sfdcFieldName) == null) {
+ // we only set the field if it didn't have a value.
+ logger.info("Setting SFDC field {} value to {}", sfdcFieldName, unomiPropertyValue);
+ sfdcLeadFields.put(sfdcFieldName, unomiPropertyValue);
+ } else {
+ // current strategy : Unomi field value wins if different from Salesforce value
+ // @todo we should probably improve this by tracking last modification dates on profile/lead properties
+ Object sfdcLeadFieldValue = existingSfdcLeadFields.get(sfdcFieldName);
+ if (!unomiPropertyValue.equals(sfdcLeadFieldValue)) {
+ logger.info("Overwriting SFDC field {} value to {}", sfdcFieldName, unomiPropertyValue);
+ sfdcLeadFields.put(sfdcFieldName, unomiPropertyValue);
+ }
+ }
+ }
+
+ if (sfdcLeadFields.size() == 0) {
+ logger.info("No SFDC field value to send, will not send anything to Salesforce.");
+ if (foundExistingSfdcLeadIds.size() == 0) {
+ return null;
+ } else {
+ return foundExistingSfdcLeadIds.iterator().next();
+ }
+ }
+
+ if (existingSfdcLeadFields.size() == 0) {
+ // if we are creating a lead, let's make sure we have all the mandatory fields before sending the request
+ boolean missingMandatoryFields = false;
+ for (String leadMandatoryFieldName : sfdcLeadMandatoryFields) {
+ if (sfdcLeadFields.get(leadMandatoryFieldName) == null) {
+ logger.warn("Missing mandatory field {}, aborting sending to Salesforce", leadMandatoryFieldName);
+ missingMandatoryFields = true;
+ }
+ }
+ if (missingMandatoryFields) {
+ return null;
+ }
+ }
+
+ String baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/sobjects/Lead";
+ HttpEntityEnclosingRequestBase request = new HttpPost(baseUrl);
+ if (foundExistingSfdcLeadIds.size() > 0) {
+ baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/sobjects/Lead/" + foundExistingSfdcLeadIds.iterator().next();
+ sfdcLeadFields.remove("Id");
+ request = new HttpPatch(baseUrl);
+ }
+
+ try {
+ ObjectMapper objectMapper = new ObjectMapper();
+ StringEntity requestEntity = new StringEntity(
+ objectMapper.writeValueAsString(sfdcLeadFields),
+ ContentType.APPLICATION_JSON);
+ request.setEntity(requestEntity);
+ Object responseObject = handleRequest(request);
+ if (responseObject == null) {
+ return null;
+ }
+ if (responseObject instanceof Map) {
+ Map<String, Object> responseData = (Map<String, Object>) responseObject;
+ if (responseData.get("id") != null) {
+ String sfdcId = (String) responseData.get("id");
+ logger.info("Lead successfully created/updated in Salesforce. sfdcId={}", sfdcId);
+ return sfdcId;
+ }
+ }
+ logger.info("Response received from Salesforce: {}", responseObject);
+ } catch (IOException e) {
+ logger.error("Error creating or updating lead for profile {}", profile, e);
+ } catch (HttpException e) {
+ logger.error("Error creating or updating lead for profile {}", profile, e);
+ }
+
+ if (foundExistingSfdcLeadIds.size() == 0) {
+ return null;
+ } else {
+ return foundExistingSfdcLeadIds.iterator().next();
+ }
+ }
+
+ @Override
+ public boolean updateProfileFromLead(Profile profile) {
+ if (!isConnected()) {
+ return false;
+ }
+ String unomiIdentifierValue = (String) profile.getProperty(sfdcConfiguration.getUnomiIdentifierField());
+ Set<String> foundSfdcLeadIds = findLeadIdsByIdentifierValue(unomiIdentifierValue);
+ if (foundSfdcLeadIds.size() == 0) {
+ logger.info("No lead found in Salesforce corresponding to profile {}", profile);
+ // we didn't find a corresponding lead in salesforce.
+ return false;
+ } else if (foundSfdcLeadIds.size() > 1) {
+ logger.warn("Found multiple leads in Salesforce for identifier value {}, will use first one.", foundSfdcLeadIds);
+ } else {
+ logger.info("Found corresponding lead with identifier value {}", unomiIdentifierValue);
+ }
+ Map<String,Object> sfdcLead = getLead(foundSfdcLeadIds.iterator().next());
+ if (sfdcLead == null) {
+ logger.error("Error retrieving lead {} from Salesforce", foundSfdcLeadIds );
+ return false;
+ }
+ boolean profileUpdated = false;
+ for (Map.Entry<String,String> sfdcToUnomiFieldMappingEntry : sfdcConfiguration.getSfdcToUnomiFieldMappings().entrySet()) {
+ String sfdcFieldName = sfdcToUnomiFieldMappingEntry.getKey();
+ String unomiFieldName = sfdcToUnomiFieldMappingEntry.getValue();
+ if (sfdcLead.get(sfdcFieldName) != null) {
+ Object sfdcFieldValue = sfdcLead.get(sfdcFieldName);
+ if (sfdcFieldValue != null && !sfdcFieldValue.equals(profile.getProperty(unomiFieldName))) {
+ profile.setProperty(unomiFieldName, sfdcFieldValue);
+ profileUpdated = true;
+ }
+ }
+ }
+ logger.info("Updated profile {} from Salesforce lead {}", profile, sfdcLead);
+ return profileUpdated;
+ }
+
+ @Override
+ public Map<String,Object> query(String query) {
+ if (!isConnected()) {
+ return null;
+ }
+ // first we must check if an existing lead exists for the profile.
+
+ String baseUrl = null;
+ try {
+ baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/query?q=" + URLEncoder.encode(query, "UTF-8");
+ HttpGet get = new HttpGet(baseUrl);
+
+ Object responseObject = handleRequest(get);
+ if (responseObject == null) {
+ return null;
+ }
+ if (responseObject != null && responseObject instanceof Map) {
+ return (Map<String,Object>) responseObject;
+ }
+ return null;
+ } catch (UnsupportedEncodingException e) {
+ logger.error("Error executing query {}", query, e);
+ return null;
+ } catch (ClientProtocolException e) {
+ logger.error("Error executing query {}", query, e);
+ return null;
+ } catch (IOException e) {
+ logger.error("Error executing query {}", query, e);
+ return null;
+ } catch (HttpException e) {
+ logger.error("Error executing query {}", query, e);
+ return null;
+ }
+ }
+
+ @Override
+ public Map<String, Object> getLimits() {
+ if (!isConnected()) {
+ return null;
+ }
+ String baseUrl = null;
+ try {
+ baseUrl = sfdcSession.getEndPoint() + REST_ENDPOINT_URI + "/limits";
+ HttpGet get = new HttpGet(baseUrl);
+
+ Object responseObject = handleRequest(get);
+ if (responseObject == null) {
+ return null;
+ }
+
+ if (responseObject instanceof Map) {
+ return (Map<String,Object>) responseObject;
+ }
+ return null;
+ } catch (UnsupportedEncodingException e) {
+ logger.error("Error retrieving Salesforce API Limits", e);
+ return null;
+ } catch (ClientProtocolException e) {
+ logger.error("Error retrieving Salesforce API Limits", e);
+ return null;
+ } catch (IOException e) {
+ logger.error("Error retrieving Salesforce API Limits", e);
+ return null;
+ } catch (HttpException e) {
+ logger.error("Error retrieving Salesforce API Limits", e);
+ return null;
+ }
+ }
+
+ private BayeuxClient makeClient() throws Exception {
+ HttpClient httpClient = new HttpClient();
+ httpClient.setConnectTimeout(CONNECTION_TIMEOUT);
+ httpClient.setTimeout(READ_TIMEOUT);
+ httpClient.start();
+
+ if (sfdcSession == null) {
+ logger.error("Invalid session !");
+ return null;
+ }
+ logger.info("Login successful!\nServer URL: " + sfdcSession.getEndPoint()
+ + "\nSession ID=" + sfdcSession.getSessionId());
+
+ Map<String, Object> options = new HashMap<String, Object>();
+ options.put(ClientTransport.TIMEOUT_OPTION, READ_TIMEOUT);
+ LongPollingTransport transport = new LongPollingTransport(
+ options, httpClient) {
+
+ @Override
+ protected void customize(ContentExchange exchange) {
+ super.customize(exchange);
+ exchange.addRequestHeader("Authorization", "OAuth " + sfdcSession.getSessionId());
+ }
+ };
+
+ BayeuxClient client = new BayeuxClient(getSalesforceStreamingEndpoint(
+ sfdcSession.getEndPoint()), transport);
+ return client;
+ }
+
+ public void setupPushListener(String channelName, ClientSessionChannel.MessageListener messageListener) throws Exception {
+ if (!isConnected()) {
+ return;
+ }
+ final BayeuxClient client = makeClient();
+ if (client == null) {
+ throw new Exception("Login failed !");
+ }
+ client.getChannel(Channel.META_HANDSHAKE).addListener
+ (new ClientSessionChannel.MessageListener() {
+ @Override
+ public void onMessage(ClientSessionChannel channel, Message message) {
+
+ logger.debug("[CHANNEL:META_HANDSHAKE]: " + message);
+
+ boolean success = message.isSuccessful();
+ if (!success) {
+ String error = (String) message.get("error");
+ if (error != null) {
+ logger.error("Error during HANDSHAKE: " + error);
+ }
+
+ Exception exception = (Exception) message.get("exception");
+ if (exception != null) {
+ logger.error("Exception during HANDSHAKE: ", exception);
+ }
+ }
+ }
+
+ });
+
+ client.getChannel(Channel.META_CONNECT).addListener(
+ new ClientSessionChannel.MessageListener() {
+ public void onMessage(ClientSessionChannel channel, Message message) {
+
+ logger.debug("[CHANNEL:META_CONNECT]: " + message);
+
+ boolean success = message.isSuccessful();
+ if (!success) {
+ String error = (String) message.get("error");
+ if (error != null) {
+ logger.error("Error during CONNECT: " + error);
+ }
+ }
+ }
+
+ });
+
+ client.getChannel(Channel.META_SUBSCRIBE).addListener(
+ new ClientSessionChannel.MessageListener() {
+
+ public void onMessage(ClientSessionChannel channel, Message message) {
+
+ logger.debug("[CHANNEL:META_SUBSCRIBE]: " + message);
+ boolean success = message.isSuccessful();
+ if (!success) {
+ String error = (String) message.get("error");
+ if (error != null) {
+ logger.error("Error during SUBSCRIBE: " + error);
+ }
+ }
+ }
+ });
+
+ client.handshake();
+ logger.debug("Waiting for handshake");
+
+ boolean handshaken = client.waitFor(10 * 1000, BayeuxClient.State.CONNECTED);
+ if (!handshaken) {
+ logger.error("Failed to handshake: " + client);
+ }
+
+ logger.info("Subscribing for channel: " + channelName);
+
+ client.getChannel(channelName).subscribe(messageListener);
+
+ }
+
+ private String getSalesforceStreamingEndpoint(String endpoint) throws MalformedURLException {
+ return new URL(endpoint + STREAMING_ENDPOINT_URI).toExternalForm();
+ }
+
+ private void setupPushTopics(String host, String sessionId) throws HttpException, IOException {
+
+ String baseUrl = host + REST_ENDPOINT_URI + "/query?q=" + URLEncoder.encode("SELECT Id from PushTopic WHERE name = 'LeadUpdates'", "UTF-8");
+ HttpGet get = new HttpGet(baseUrl);
+
+ Map<String, String> queryResponse = (Map<String, String>) handleRequest(get);
+
+ if (queryResponse != null && queryResponse.containsKey("count")) {
+ logger.info("Push topics setup successfully");
+ }
+ }
+
+ public boolean login(SFDCConfiguration sfdcConfiguration)
+ throws HttpException, IOException {
+ String baseUrl = sfdcConfiguration.getSfdcLoginEndpoint() + "/services/oauth2/token";
+ HttpPost oauthPost = new HttpPost(baseUrl);
+ List<BasicNameValuePair> parametersBody = new ArrayList<>();
+ parametersBody.add(new BasicNameValuePair("grant_type", "password"));
+ parametersBody.add(new BasicNameValuePair("username", sfdcConfiguration.getSfdcUserUsername()));
+ parametersBody.add(new BasicNameValuePair("password", sfdcConfiguration.getSfdcUserPassword() + sfdcConfiguration.getSfdcUserSecurityToken()));
+ parametersBody.add(new BasicNameValuePair("client_id", sfdcConfiguration.getSfdcConsumerKey()));
+ parametersBody.add(new BasicNameValuePair("client_secret", sfdcConfiguration.getSfdcConsumerSecret()));
+ oauthPost.setEntity(new UrlEncodedFormEntity(parametersBody, "UTF-8"));
+
+ Map<String, String> oauthLoginResponse = (Map<String,String>) handleRequest(oauthPost, 0, false);
+ if (oauthLoginResponse == null) {
+ return false;
+ }
+
+ sfdcSession = new SFDCSession(
+ oauthLoginResponse.get("access_token"),
+ oauthLoginResponse.get("instance_url"),
+ oauthLoginResponse.get("signature"),
+ oauthLoginResponse.get("id"),
+ oauthLoginResponse.get("token_type"),
+ oauthLoginResponse.get("issued_at"),
+ sfdcConfiguration.getSfdcSessionTimeout());
+ return true;
+ }
+
+ public void logout() {
+ sfdcSession = null;
+ }
+
+ private SFDCSession getValidSession() {
+ if (isSessionValid()) {
+ return sfdcSession;
+ }
+ boolean loginSuccessful = false;
+ try {
+ loginSuccessful = login(sfdcConfiguration);
+ if (loginSuccessful && sfdcSession != null) {
+ return sfdcSession;
+ }
+ } catch (HttpException e) {
+ logger.error("Error logging in", e);
+ return null;
+ } catch (IOException e) {
+ logger.error("Error logging in", e);
+ return null;
+ }
+ return null;
+ }
+
+ private boolean isSessionValid() {
+ if (sfdcSession == null) {
+ return false;
+ }
+ if (sfdcSession.isExpired()) {
+ return false;
+ }
+ return true;
+ }
+
+ private Object handleRequest(HttpUriRequest request) throws IOException, HttpException {
+ return handleRequest(request, 1, true);
+ }
+
+ private Object handleRequest(HttpUriRequest request, int retryCount, boolean addAuthorizationHeader) throws IOException, HttpException {
+ CloseableHttpClient client = HttpClientBuilder.create().build();
+ if (addAuthorizationHeader) {
+ SFDCSession sfdcSession = getValidSession();
+ if (sfdcSession == null) {
+ logger.error("Couldn't get a valid session !");
+ return null;
+ }
+ if (request.containsHeader("Authorization")) {
+ logger.debug("Replacing existing authorization header with an updated one.");
+ Header[] authorizationHeaders = request.getHeaders("Authorization");
+ for (Header authorizationHeader : authorizationHeaders) {
+ request.removeHeader(authorizationHeader);
+ }
+ }
+ request.addHeader("Authorization", "Bearer " + sfdcSession.getSessionId());
+ }
+
+ CloseableHttpResponse response = client.execute(request);
+ if (response.getStatusLine().getStatusCode() >= 400) {
+ if ((response.getStatusLine().getStatusCode() == 401 || response.getStatusLine().getStatusCode() == 403) && retryCount > 0) {
+ // probably the session has expired, let's try to login again
+ logger.warn("Unauthorized request, attempting to login again...");
+ boolean loginSuccessful = login(sfdcConfiguration);
+ if (!loginSuccessful) {
+ logger.error("Login failed, cannot execute request {}", request);
+ return null;
+ }
+ logger.warn("Retrying request {} once again...", request);
+ return handleRequest(request, 0, true);
+ } else {
+ logger.error("Error executing request {}: {}-{}", request, response.getStatusLine().getStatusCode(), response.getStatusLine().getStatusCode());
+ if (response.getEntity() != null) {
+ logger.error("Entity={}", EntityUtils.toString(response.getEntity()));
+ }
+ }
+ return null;
+ }
+ if (response.getEntity() == null) {
+ return null;
+ }
+ return JSON.parse(EntityUtils.toString(response.getEntity()));
+ }
+
+ public boolean isConfigured() {
+ if (!sfdcConfiguration.isComplete()) {
+ logger.warn("Connection to Salesforce is not properly configured !");
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isConnected() {
+ if (!isConfigured()) {
+ return false;
+ }
+ if (sfdcSession == null) {
+ logger.warn("Not connected to SalesForce, operation will not execute.");
+ return false;
+ } else {
+ if (sfdcSession.isExpired()) {
+ logger.warn("Connection to Salesforce has expired, will reconnect on next request");
+ return true;
+ }
+ }
+ return true;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/salesforce-connector/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..1b0b0ce
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
+ xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
+
+ <cm:property-placeholder persistent-id="org.apache.unomi.sfdc"
+ update-strategy="reload">
+ <cm:default-properties>
+ <cm:property name="sfdc.login.endpoint" value="https://login.salesforce.com"/>
+ <cm:property name="sfdc.user.username" value=""/>
+ <cm:property name="sfdc.user.password" value=""/>
+ <cm:property name="sfdc.user.securityToken" value=""/>
+ <cm:property name="sfdc.consumer.key" value=""/>
+ <cm:property name="sfdc.consumer.secret" value=""/>
+ <cm:property name="sfdc.channel" value="/topic/LeadUpdates"/>
+ <cm:property name="sfdc.fields.mappings.identifier" value=""/>
+ <cm:property name="sfdc.fields.mappings" value=""/>
+ <cm:property name="sfdc.session.timeout" value="900000"/>
+ </cm:default-properties>
+ </cm:property-placeholder>
+
+ <reference id="persistenceService"
+ interface="org.apache.unomi.persistence.spi.PersistenceService"/>
+
+ <bean id="defaultSFDCConfiguration" class="org.apache.unomi.sfdc.services.SFDCConfiguration">
+ <property name="itemId" value="sfdcConfiguration"/>
+ <property name="sfdcLoginEndpoint" value="${sfdc.login.endpoint}"/>
+ <property name="sfdcUserUsername" value="${sfdc.user.username}"/>
+ <property name="sfdcUserPassword" value="${sfdc.user.password}"/>
+ <property name="sfdcUserSecurityToken" value="${sfdc.user.securityToken}"/>
+ <property name="sfdcConsumerKey" value="${sfdc.consumer.key}"/>
+ <property name="sfdcConsumerSecret" value="${sfdc.consumer.secret}"/>
+ <property name="sfdcChannel" value="${sfdc.channel}"/>
+ <property name="sfdcFieldMappings" value="${sfdc.fields.mappings}"/>
+ <property name="sfdcFieldMappingsIdentifier" value="${sfdc.fields.mappings.identifier}"/>
+ <property name="sfdcSessionTimeout" value="${sfdc.session.timeout}" />
+ </bean>
+
+ <bean id="sfdcServiceImpl" class="org.apache.unomi.sfdc.services.internal.SFDCServiceImpl" init-method="start"
+ destroy-method="stop">
+ <property name="defaultSFDCConfiguration" ref="defaultSFDCConfiguration" />
+ <property name="persistenceService" ref="persistenceService" />
+ </bean>
+
+ <service id="sfdcService" ref="sfdcServiceImpl" auto-export="interfaces"/>
+
+</blueprint>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/resources/org.apache.unomi.sfdc.cfg
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/resources/org.apache.unomi.sfdc.cfg b/extensions/salesforce-connector/services/src/main/resources/org.apache.unomi.sfdc.cfg
new file mode 100644
index 0000000..939bddb
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/resources/org.apache.unomi.sfdc.cfg
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+sfdc.login.endpoint=https://login.salesforce.com
+sfdc.user.username=
+sfdc.user.password=
+sfdc.user.securityToken=
+sfdc.consumer.key=
+sfdc.consumer.secret=
+sfdc.channel=/topic/LeadUpdates
+sfdc.fields.mappings=email=Email,firstName=FirstName,lastName=LastName,company=Company,phoneNumber=Phone,jobTitle=Title,city=City,zipCode=PostalCode,address=Street,sfdcStatus=Status,sfdcRating=Rating
+sfdc.fields.mappings.identifier=email=Email
+sfdc.session.timeout=900000
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/test/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImplTest.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/test/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImplTest.java b/extensions/salesforce-connector/services/src/test/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImplTest.java
new file mode 100644
index 0000000..2a566a4
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/test/java/org/apache/unomi/sfdc/services/internal/SFDCServiceImplTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.unomi.sfdc.services.internal;
+
+import org.apache.http.HttpException;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.sfdc.services.SFDCConfiguration;
+import org.cometd.bayeux.Message;
+import org.cometd.bayeux.client.ClientSessionChannel;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.junit.Assert.*;
+
+/**
+ * A unit test class for testing the Salesforce Service implementation
+ */
+public class SFDCServiceImplTest {
+
+ private static SFDCServiceImpl sfdcServiceImpl;
+ private static boolean canRunTests = false;
+ private static SFDCConfiguration sfdcConfiguration;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ sfdcServiceImpl = new SFDCServiceImpl();
+ // we must now configure it.
+ InputStream configPropertiesStream = SFDCServiceImplTest.class.getClassLoader().getResourceAsStream("org.apache.unomi.sfdc.cfg");
+ Properties properties = new Properties();
+ properties.load(configPropertiesStream);
+ sfdcConfiguration = new SFDCConfiguration();
+ sfdcConfiguration.setSfdcLoginEndpoint(properties.getProperty("sfdc.login.endpoint"));
+ sfdcConfiguration.setSfdcUserUsername(properties.getProperty("sfdc.user.username"));
+ sfdcConfiguration.setSfdcUserPassword(properties.getProperty("sfdc.user.password"));
+ sfdcConfiguration.setSfdcUserSecurityToken(properties.getProperty("sfdc.user.securityToken"));
+ sfdcConfiguration.setSfdcConsumerKey(properties.getProperty("sfdc.consumer.key"));
+ sfdcConfiguration.setSfdcConsumerSecret(properties.getProperty("sfdc.consumer.secret"));
+ sfdcConfiguration.setSfdcChannel(properties.getProperty("sfdc.channel"));
+ sfdcConfiguration.setSfdcFieldMappings(properties.getProperty("sfdc.fields.mappings"));
+ sfdcConfiguration.setSfdcFieldMappingsIdentifier(properties.getProperty("sfdc.fields.mappings.identifier"));
+ if (System.getProperty("sfdcProperties") != null) {
+ Properties testProperties = new Properties();
+ InputStream testPropertiesInputStream = new FileInputStream(System.getProperty("sfdcProperties"));
+ testProperties.load(testPropertiesInputStream);
+ sfdcConfiguration.setSfdcLoginEndpoint(testProperties.getProperty("sfdc.login.endpoint"));
+ sfdcConfiguration.setSfdcUserUsername(testProperties.getProperty("sfdc.user.username"));
+ sfdcConfiguration.setSfdcUserPassword(testProperties.getProperty("sfdc.user.password"));
+ sfdcConfiguration.setSfdcUserSecurityToken(testProperties.getProperty("sfdc.user.securityToken"));
+ sfdcConfiguration.setSfdcConsumerKey(testProperties.getProperty("sfdc.consumer.key"));
+ sfdcConfiguration.setSfdcConsumerSecret(testProperties.getProperty("sfdc.consumer.secret"));
+ canRunTests = true;
+ sfdcServiceImpl.setDefaultSFDCConfiguration(sfdcConfiguration);
+ sfdcServiceImpl.start();
+ } else {
+ System.out.println("CANNOT RUN TESTS, PLEASE PROVIDE A PROPERTIES FILE WITH SALESFORCE CREDENTIALS AND REFERENCING IT USING -DsfdcProperties=FILEPATH !!!!!!");
+ }
+ }
+
+ @AfterClass
+ public static void shutdown() {
+ if (canRunTests) {
+ sfdcServiceImpl.stop();
+ sfdcServiceImpl = null;
+ }
+ }
+
+ private boolean checkCanRunTests() {
+ if (!canRunTests) {
+ System.out.println("CANNOT RUN TESTS, PLEASE PROVIDE A PROPERTIES FILE WITH SALESFORCE CREDENTIALS AND REFERENCING IT USING -DsfdcProperties=FILEPATH !!!!!!");
+ }
+ return canRunTests;
+ }
+
+ @Test
+ public void testGetLeads() {
+ if (!checkCanRunTests()) return;
+ Set<String> recentLeadIds = sfdcServiceImpl.getRecentLeadIds();
+ if (recentLeadIds == null || recentLeadIds.size() == 0) {
+ return;
+ }
+ for (String recentLeadId : recentLeadIds) {
+ Map<String,Object> leadFields = sfdcServiceImpl.getLead(recentLeadId);
+ if (leadFields.containsKey(sfdcConfiguration.getSfdcIdentifierField())) {
+ String leadIdentifierFieldValue = (String) leadFields.get(sfdcConfiguration.getSfdcIdentifierField());
+ if (leadIdentifierFieldValue == null) {
+ System.out.println("Skipping lead with null identifier field value for field: " + sfdcConfiguration.getSfdcIdentifierField());
+ continue;
+ }
+ Set<String> foundLeadIds = sfdcServiceImpl.findLeadIdsByIdentifierValue(leadIdentifierFieldValue);
+ assertTrue("Should find a single lead for identifier value " + leadIdentifierFieldValue, foundLeadIds.size() == 1);
+ assertEquals("Expected Id to be the same", foundLeadIds.iterator().next(), leadFields.get("Id"));
+ }
+ }
+ }
+
+ @Test
+ public void testGetLimits() {
+ if (!checkCanRunTests()) return;
+ Map<String,Object> limits = sfdcServiceImpl.getLimits();
+ assertNotNull("Limits object is null, an error occurred !", limits);
+ }
+
+ @Test
+ public void testCreateOrUpdateAndSyncLead() {
+ if (!checkCanRunTests()) return;
+ Profile profile = new Profile();
+ profile.setItemId(UUID.randomUUID().toString());
+ profile.setProperty("email", "test2@jahia.com");
+ profile.setProperty("firstName", "Serge");
+ String leadId = sfdcServiceImpl.createOrUpdateLead(profile);
+ assertNull("The lead creation should fail since we are missing mandatory fields.", leadId);
+ profile.setProperty("lastName", "Huber");
+ profile.setProperty("company", "Jahia Solutions Group");
+ profile.setProperty("phoneNumber", "+41223613424");
+ profile.setProperty("jobTitle", "CTO");
+ leadId = sfdcServiceImpl.createOrUpdateLead(profile);
+ // now let's try to update it.
+ profile.setProperty("company", "Jahia Solutions Group SA");
+ sfdcServiceImpl.createOrUpdateLead(profile);
+ boolean profileUpdated = sfdcServiceImpl.updateProfileFromLead(profile);
+ assertTrue("Profile should have been updated since we are reading status field", profileUpdated);
+ profile.setProperty("company", "Another value");
+ profileUpdated = sfdcServiceImpl.updateProfileFromLead(profile);
+ assertTrue("Profile should have been updated since data is not equal", profileUpdated);
+ if (leadId != null) {
+ sfdcServiceImpl.deleteLead(leadId);
+ }
+ }
+
+ @Test
+ public void testStreaming() throws Exception {
+ if (!checkCanRunTests()) return;
+ System.out.println("Running streaming client example....");
+
+ sfdcServiceImpl.setupPushListener(sfdcConfiguration.getSfdcChannel(), new ClientSessionChannel.MessageListener() {
+ @Override
+ public void onMessage(ClientSessionChannel clientSessionChannel, Message message) {
+ System.out.println("Received message for channel" + sfdcConfiguration.getSfdcChannel() + ":"+ message);
+ }
+ });
+
+ System.out.println("Waiting 10 seconds for streamed data from your organization ...");
+ int i=0;
+ while (i < 10) {
+ Thread.sleep(1000);
+ i++;
+ }
+
+ }
+
+ @Test
+ public void testFailedLogin() throws IOException, HttpException {
+ if (!checkCanRunTests()) return;
+ InputStream configPropertiesStream = SFDCServiceImplTest.class.getClassLoader().getResourceAsStream("org.apache.unomi.sfdc.cfg");
+ Properties properties = new Properties();
+ properties.load(configPropertiesStream);
+ String loginEndpoint = properties.getProperty("sfdc.login.endpoint");
+ Properties testProperties = new Properties();
+ if (System.getProperty("sfdcProperties") != null) {
+ sfdcServiceImpl.logout();
+ InputStream testPropertiesInputStream = new FileInputStream(System.getProperty("sfdcProperties"));
+ testProperties.load(testPropertiesInputStream);
+ if (testProperties.getProperty("sfdc.login.endpoint") != null) {
+ loginEndpoint = testProperties.getProperty("sfdc.login.endpoint");
+ }
+ String userUserName = testProperties.getProperty("sfdc.user.username");
+ String userPassword = testProperties.getProperty("sfdc.user.password");
+ String userSecurityToken = testProperties.getProperty("sfdc.user.securityToken");
+ String consumerKey = testProperties.getProperty("sfdc.consumer.key");
+ String consumerSecret = testProperties.getProperty("sfdc.consumer.secret");
+ SFDCConfiguration sfdcConfiguration = new SFDCConfiguration();
+ sfdcConfiguration.setSfdcLoginEndpoint(loginEndpoint);
+ sfdcConfiguration.setSfdcUserUsername(userUserName);
+ sfdcConfiguration.setSfdcUserPassword(userPassword + "wrongpassword");
+ sfdcConfiguration.setSfdcUserSecurityToken(userSecurityToken);
+ sfdcConfiguration.setSfdcConsumerKey(consumerKey);
+ sfdcConfiguration.setSfdcConsumerSecret(consumerSecret);
+ boolean loginSuccessful = sfdcServiceImpl.login(sfdcConfiguration);
+ assertNull("Session should not be valid since we used a wrong password !", sfdcServiceImpl.getSFDCSession());
+
+ // we login properly for other tests to execute properly.
+ sfdcConfiguration.setSfdcUserPassword(userPassword);
+ loginSuccessful = sfdcServiceImpl.login(sfdcConfiguration);
+ assertTrue("Login with proper credentials should have worked !", loginSuccessful);
+ }
+ }
+}
[2/2] incubator-unomi git commit: UNOMI-116 Salesforce CRM Connector
- Initial commit
Posted by sh...@apache.org.
UNOMI-116 Salesforce CRM Connector
- Initial commit
Signed-off-by: Serge Huber <sh...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/incubator-unomi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-unomi/commit/a897ab84
Tree: http://git-wip-us.apache.org/repos/asf/incubator-unomi/tree/a897ab84
Diff: http://git-wip-us.apache.org/repos/asf/incubator-unomi/diff/a897ab84
Branch: refs/heads/master
Commit: a897ab8437966975a45082cb042c500540c8c0f8
Parents: 139e1b7
Author: Serge Huber <sh...@apache.org>
Authored: Wed Jul 19 14:28:54 2017 +0200
Committer: Serge Huber <sh...@apache.org>
Committed: Wed Jul 19 14:28:54 2017 +0200
----------------------------------------------------------------------
.gitignore | 1 +
extensions/pom.xml | 1 +
extensions/salesforce-connector/README.md | 148 ++++
extensions/salesforce-connector/actions/pom.xml | 49 ++
.../sfdc/actions/CreateOrUpdateLeadAction.java | 42 +
.../actions/UpdateProfileFromLeadAction.java | 43 +
.../cxs/actions/sfdcCreateOrUpdateLead.json | 15 +
.../cxs/actions/sfdcUpdateProfileFromLead.json | 15 +
.../resources/OSGI-INF/blueprint/blueprint.xml | 44 +
.../salesforce-connector/compileWithTests.sh | 20 +
.../salesforce-connector/karaf-kar/pom.xml | 93 +++
.../karaf-kar/src/main/feature/feature.xml | 28 +
extensions/salesforce-connector/pom.xml | 39 +
extensions/salesforce-connector/rest/pom.xml | 132 +++
.../apache/unomi/sfdc/rest/SFDCEndPoint.java | 81 ++
.../resources/OSGI-INF/blueprint/blueprint.xml | 67 ++
.../salesforce-connector/services/pom.xml | 164 ++++
.../unomi/sfdc/services/FieldMapping.java | 35 +
.../apache/unomi/sfdc/services/MappedField.java | 30 +
.../unomi/sfdc/services/SFDCConfiguration.java | 178 ++++
.../apache/unomi/sfdc/services/SFDCService.java | 81 ++
.../apache/unomi/sfdc/services/SFDCSession.java | 71 ++
.../sfdc/services/internal/SFDCServiceImpl.java | 815 +++++++++++++++++++
.../resources/OSGI-INF/blueprint/blueprint.xml | 65 ++
.../main/resources/org.apache.unomi.sfdc.cfg | 26 +
.../services/internal/SFDCServiceImplTest.java | 211 +++++
26 files changed, 2494 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index bf90830..c3e5009 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ maven-metadata-local.xml
GeoLite2-City.mmdb
allCountries.zip
rest/.miredot-offline.json
+/extensions/salesforce-connector/test.properties
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/pom.xml b/extensions/pom.xml
index dea4bc2..07608b3 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -35,6 +35,7 @@
<module>privacy-extension</module>
<module>geonames</module>
<module>router</module>
+ <module>salesforce-connector</module>
</modules>
</project>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/README.md
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/README.md b/extensions/salesforce-connector/README.md
new file mode 100644
index 0000000..4bd7451
--- /dev/null
+++ b/extensions/salesforce-connector/README.md
@@ -0,0 +1,148 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+Unomi Salesforce Connector
+==========================
+
+## Getting started
+
+1. Create a new developer account here:
+
+ https://developer.salesforce.com/signup
+
+2. Create a new Connected App, by going into Setup -> App Manager and click "Create Connected App"
+
+3. In the settings, make sure you do the following:
+
+ Enable OAuth settings -> Activated
+ Enable for device flow -> Activated (no need for a callback URL)
+ Add all the selected OAuth scopes you want (or put all of them)
+ Make sure Require Secret for Web Server flow is activated
+
+4. Make sure you retrieve the following information once you have created the app in the API (Enable OAuth Settings):
+
+ Consumer key
+ Consumer secret (click to see it)
+
+5. You must also retrieve your user's security token, or create it if you don't have one already. To do this simply
+click on your user at the top right, select "Settings", the click on "Reset my security token". You will receive an email
+with the security token.
+
+6. You are now ready to configure the Apache Unomi Salesforce Connector. In the etc/org.apache.unomi.sfdc.cfg file
+change the following settings:
+
+ sfdc.user.username=YOUR_USER_NAME
+ sfdc.user.password=YOUR_PASSWORD
+ sfdc.user.securityToken=YOUR_USER_SECURITY_TOKEN
+ sfdc.consumer.key=CONNECTED_APP_CONSUMER_KEY
+ sfdc.consumer.secret=CONNECTED_APP_SECRET
+
+7. Connected to the Apache Unomi Karaf Shell using :
+
+ ssh -p 8102 karaf@localhost (default password is karaf)
+
+7. Deploy into Apache Unomi using the following commands from the Apache Karaf shell:
+
+ feature:repo-add mvn:org.apache.unomi/unomi-salesforce-connector-karaf-kar/1.2.0-incubating-SNAPSHOT/xml/features
+ feature:install unomi-salesforce-connector-karaf-kar
+
+8. You can then test the connection to Salesforce by accessing the following URLs:
+
+ https://localhost:9443/cxs/sfdc/version
+ https://localhost:9443/cxs/sfdc/limits
+
+ The first URL will give you information about the version of the connector, so this makes it easy to check that the
+ plugin is properly deployed, started and the correct version. The second URL will actually make a request to the
+ Salesforce REST API to retrieve the limits of the Salesforce API.
+
+ Both URLs are password protected by the Apache Unomi (Karaf) password. You can find this user and password information
+ in the etc/users.properties file.
+
+## Upgrading the Salesforce connector
+
+If you followed all the steps in the Getting Started section, you can upgrade the Salesforce connector by using the following steps:
+
+1. Compile the connector using:
+
+ mvn clean install
+
+2. Login to the Unomi Karaf Shell using:
+
+ ssh -p 8102 karaf@localhost (password by default is karaf)
+
+3. Execute the following commands in the Karaf shell
+
+ feature:repo-refresh
+ feature:uninstall unomi-salesforce-connector-karaf-feature
+ feature:install unomi-salesforce-connector-karaf-feature
+
+4. You can then check that the new version is properly deployed by accessing the following URL and checking the build date:
+
+ https://localhost:9443/cxs/sfdc/version
+
+ (if asked for a password it's the same karaf/karaf default)
+
+## Using the Salesforce Workbench for testing REST API
+
+The Salesforce Workbench contains a REST API Explorer that is very useful to test requests. You may find it here :
+
+ https://workbench.developerforce.com/restExplorer.php
+
+## Setting up Streaming Push queries
+
+Using the Salesforce Workbench, you can setting streaming push queries (Queries->Streaming push topics) such as the
+following example:
+
+ Name: LeadUpdates
+ Query : SELECT Id,FirstName,LastName,Email,Company FROM Lead
+
+## Executing the unit tests
+
+Before running the tests, make sure you have completed all the steps above, including the streaming push queries setup.
+
+By default the unit tests will not run as they need proper Salesforce credentials to run. To set this up create a
+properties file like the following one:
+
+test.properties
+
+ #
+ # Licensed to the Apache Software Foundation (ASF) under one or more
+ # contributor license agreements. See the NOTICE file distributed with
+ # this work for additional information regarding copyright ownership.
+ # The ASF licenses this file to You 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.
+ #
+ sfdc.user.username=YOUR_USER_NAME
+ sfdc.user.password=YOUR_PASSWORD
+ sfdc.user.securityToken=YOUR_USER_SECURITY_TOKEN
+ sfdc.consumer.key=CONNECTED_APP_CONSUMER_KEY
+ sfdc.consumer.secret=CONNECTED_APP_SECRET
+
+and then use the following command line to reference the file:
+
+ mvn clean install -DsfdcProperties=../test.properties
+
+(in case you're wondering the ../ is because the test is located in the services sub-directory)
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/actions/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/actions/pom.xml b/extensions/salesforce-connector/actions/pom.xml
new file mode 100644
index 0000000..f0b47b9
--- /dev/null
+++ b/extensions/salesforce-connector/actions/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>unomi-salesforce-connector-actions</artifactId>
+ <name>Apache Unomi :: Extensions :: Salesforce connector :: Rule Actions</name>
+ <description>Rule Actions for the Apache Unomi Context Server extension that integrates with Salesforce</description>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-api</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector-services</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/CreateOrUpdateLeadAction.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/CreateOrUpdateLeadAction.java b/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/CreateOrUpdateLeadAction.java
new file mode 100644
index 0000000..e46ec29
--- /dev/null
+++ b/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/CreateOrUpdateLeadAction.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.unomi.sfdc.actions;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.actions.Action;
+import org.apache.unomi.api.actions.ActionExecutor;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.sfdc.services.SFDCService;
+
+/**
+ * Creates or updates a Salesforce lead from the corresponding Apache Unomi profile (using a common identifier field,
+ * usually the email address)
+ */
+public class CreateOrUpdateLeadAction implements ActionExecutor {
+
+ private SFDCService sfdcService;
+
+ public void setSfdcService(SFDCService sfdcService) {
+ this.sfdcService = sfdcService;
+ }
+
+ @Override
+ public int execute(Action action, Event event) {
+ sfdcService.createOrUpdateLead(event.getProfile());
+ return EventService.NO_CHANGE;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/UpdateProfileFromLeadAction.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/UpdateProfileFromLeadAction.java b/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/UpdateProfileFromLeadAction.java
new file mode 100644
index 0000000..bbbd5c1
--- /dev/null
+++ b/extensions/salesforce-connector/actions/src/main/java/org/apache/unomi/sfdc/actions/UpdateProfileFromLeadAction.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.unomi.sfdc.actions;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.actions.Action;
+import org.apache.unomi.api.actions.ActionExecutor;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.sfdc.services.SFDCService;
+
+/**
+ * Update an Apache Unomi profile from a corresponding Salesforce Lead with the same common identifier (usually the
+ * email address)
+ */
+public class UpdateProfileFromLeadAction implements ActionExecutor {
+
+ private SFDCService sfdcService;
+
+ public void setSfdcService(SFDCService sfdcService) {
+ this.sfdcService = sfdcService;
+ }
+
+ @Override
+ public int execute(Action action, Event event) {
+ boolean profileUpdated = sfdcService.updateProfileFromLead(event.getProfile());
+ if (profileUpdated) return EventService.PROFILE_UPDATED;
+ return EventService.NO_CHANGE;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcCreateOrUpdateLead.json
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcCreateOrUpdateLead.json b/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcCreateOrUpdateLead.json
new file mode 100644
index 0000000..55c7da3
--- /dev/null
+++ b/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcCreateOrUpdateLead.json
@@ -0,0 +1,15 @@
+{
+ "metadata": {
+ "id": "sfdcCreateOrUpdateLeadAction",
+ "name": "sfdcCreateOrUpdateLeadAction",
+ "description": "",
+ "tags": [
+ "demographic",
+ "hidden.availableToEndUser"
+ ],
+ "readOnly": true
+ },
+ "actionExecutor": "sfdcCreateOrUpdateLead",
+ "parameters": [
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcUpdateProfileFromLead.json
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcUpdateProfileFromLead.json b/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcUpdateProfileFromLead.json
new file mode 100644
index 0000000..598edb7
--- /dev/null
+++ b/extensions/salesforce-connector/actions/src/main/resources/META-INF/cxs/actions/sfdcUpdateProfileFromLead.json
@@ -0,0 +1,15 @@
+{
+ "metadata": {
+ "id": "sfdcUpdateProfileFromLeadAction",
+ "name": "sfdcUpdateProfileFromLeadAction",
+ "description": "",
+ "tags": [
+ "demographic",
+ "hidden.availableToEndUser"
+ ],
+ "readOnly": true
+ },
+ "actionExecutor": "sfdcUpdateProfileFromLead",
+ "parameters": [
+ ]
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/actions/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/actions/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/salesforce-connector/actions/src/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..24b61ff
--- /dev/null
+++ b/extensions/salesforce-connector/actions/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
+
+ <reference id="sfdcService"
+ interface="org.apache.unomi.sfdc.services.SFDCService"/>
+
+ <service auto-export="interfaces">
+ <service-properties>
+ <entry key="actionExecutorId" value="sfdcCreateOrUpdateLead"/>
+ </service-properties>
+ <bean class="org.apache.unomi.sfdc.actions.CreateOrUpdateLeadAction">
+ <property name="sfdcService" ref="sfdcService"/>
+ </bean>
+ </service>
+
+ <service auto-export="interfaces">
+ <service-properties>
+ <entry key="actionExecutorId" value="sfdcUpdateProfileFromLead"/>
+ </service-properties>
+ <bean class="org.apache.unomi.sfdc.actions.UpdateProfileFromLeadAction">
+ <property name="sfdcService" ref="sfdcService"/>
+ </bean>
+ </service>
+
+</blueprint>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/compileWithTests.sh
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/compileWithTests.sh b/extensions/salesforce-connector/compileWithTests.sh
new file mode 100755
index 0000000..5295f7a
--- /dev/null
+++ b/extensions/salesforce-connector/compileWithTests.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+################################################################################
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+################################################################################
+mvn clean install -DsfdcProperties=../test.properties
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/karaf-kar/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/karaf-kar/pom.xml b/extensions/salesforce-connector/karaf-kar/pom.xml
new file mode 100644
index 0000000..577f4bf
--- /dev/null
+++ b/extensions/salesforce-connector/karaf-kar/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>unomi-salesforce-connector-karaf-kar</artifactId>
+ <name>Apache Unomi :: Extensions :: Salesforce connector :: Apache Karaf Feature and KAR archive</name>
+ <description>Apache Karaf Feature and KAR archive for the Apache Unomi Context Server extension that integrates with Salesforce</description>
+ <packaging>kar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-api</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore-osgi</artifactId>
+ <version>4.4.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient-osgi</artifactId>
+ <version>4.5.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector-services</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector-rest</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector-actions</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <includeTransitiveDependency>false</includeTransitiveDependency>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <configuration>
+ <startLevel>85</startLevel>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/karaf-kar/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/karaf-kar/src/main/feature/feature.xml b/extensions/salesforce-connector/karaf-kar/src/main/feature/feature.xml
new file mode 100644
index 0000000..252f34e
--- /dev/null
+++ b/extensions/salesforce-connector/karaf-kar/src/main/feature/feature.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.1" name="unomi-salesforce-connector-karaf-feature">
+ <feature name="unomi-salesforce-connector-karaf-kar" version="1.0-SNAPSHOT" description="Apache Unomi :: Extensions :: Salesforce connector :: Apache Karaf Feature">
+ <details>Apache Karaf feature for the Apache Unomi Context Server extension that integrates with Salesforce</details>
+ <configfile finalname="/etc/org.apache.unomi.sfdc.cfg">mvn:org.apache.unomi/unomi-salesforce-connector-services/1.0-SNAPSHOT/cfg/sfdccfg</configfile>
+ <bundle start-level="85">mvn:org.apache.httpcomponents/httpcore-osgi/4.4.6</bundle>
+ <bundle start-level="85">mvn:org.apache.httpcomponents/httpclient-osgi/4.5.1</bundle>
+ <bundle start-level="85">mvn:org.apache.unomi/unomi-salesforce-connector-services/1.0-SNAPSHOT</bundle>
+ <bundle start-level="85">mvn:org.apache.unomi/unomi-salesforce-connector-rest/1.0-SNAPSHOT</bundle>
+ <bundle start-level="85">mvn:org.apache.unomi/unomi-salesforce-connector-actions/1.0-SNAPSHOT</bundle>
+ </feature>
+</features>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/pom.xml b/extensions/salesforce-connector/pom.xml
new file mode 100644
index 0000000..da00cf9
--- /dev/null
+++ b/extensions/salesforce-connector/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-extensions</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>unomi-salesforce-connector</artifactId>
+ <name>Apache Unomi :: Extensions :: Salesforce connector</name>
+ <description>Apache Unomi Context Server extension that integrates with Salesforce</description>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>services</module>
+ <module>rest</module>
+ <module>actions</module>
+ <module>karaf-kar</module>
+ </modules>
+
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/rest/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/rest/pom.xml b/extensions/salesforce-connector/rest/pom.xml
new file mode 100644
index 0000000..2420cc5
--- /dev/null
+++ b/extensions/salesforce-connector/rest/pom.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>unomi-salesforce-connector-rest</artifactId>
+ <name>Apache Unomi :: Extensions :: Salesforce connector :: REST API</name>
+ <description>REST API for the Apache Unomi Context Server extension that integrates with Salesforce</description>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-api</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector-services</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!--
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-core</artifactId>
+ <version>${cxf.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxws</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-transports-http</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-security-cors</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-persistence-spi</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>buildnumber-maven-plugin</artifactId>
+ <version>1.4</version>
+ <configuration>
+ <format>{0}-{1,date,short}-{1,time,short}</format>
+ <items>
+ <item>scmVersion</item>
+ <item>timestamp</item>
+ </items>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>validate</phase>
+ <goals>
+ <goal>create</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Implementation-Build>${buildNumber}</Implementation-Build>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/rest/src/main/java/org/apache/unomi/sfdc/rest/SFDCEndPoint.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/rest/src/main/java/org/apache/unomi/sfdc/rest/SFDCEndPoint.java b/extensions/salesforce-connector/rest/src/main/java/org/apache/unomi/sfdc/rest/SFDCEndPoint.java
new file mode 100644
index 0000000..c7a5c3f
--- /dev/null
+++ b/extensions/salesforce-connector/rest/src/main/java/org/apache/unomi/sfdc/rest/SFDCEndPoint.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.unomi.sfdc.rest;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.sfdc.services.SFDCService;
+import org.osgi.framework.BundleContext;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+@WebService
+@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+@CrossOriginResourceSharing(
+ allowAllOrigins = true,
+ allowCredentials = true
+)
+public class SFDCEndPoint {
+
+ private SFDCService sfdcService;
+ private BundleContext bundleContext;
+
+ public SFDCEndPoint() {
+ System.out.println("Initializing SFDC service endpoint...");
+ }
+
+ @WebMethod(exclude = true)
+ public void setSFDCService(SFDCService sfdcService) {
+ this.sfdcService = sfdcService;
+ }
+
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ @GET
+ @Path("/version")
+ public Map<String,String> getVersion() {
+ Map<String,String> versionInfo = new HashMap<>();
+ Dictionary<String,String> bundleHeaders = bundleContext.getBundle().getHeaders();
+ Enumeration<String> bundleHeaderKeyEnum = bundleHeaders.keys();
+ while (bundleHeaderKeyEnum.hasMoreElements()) {
+ String bundleHeaderKey = bundleHeaderKeyEnum.nextElement();
+ versionInfo.put(bundleHeaderKey, bundleHeaders.get((bundleHeaderKey)));
+ }
+ return versionInfo;
+ }
+
+ @GET
+ @Path("/limits")
+ public Map<String,Object> getLimits() {
+ return sfdcService.getLimits();
+ }
+
+ public void setSfdcService(SFDCService sfdcService) {
+ this.sfdcService = sfdcService;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/salesforce-connector/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..30c8a81
--- /dev/null
+++ b/extensions/salesforce-connector/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
+ xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
+ http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd">
+
+ <bean id="cors-filter" class="org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter"/>
+ <bean id="jacksonMapper" class="org.apache.unomi.persistence.spi.CustomObjectMapper"/>
+ <bean id="jaxb-provider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider">
+ <argument index="0" ref="jacksonMapper" type="com.fasterxml.jackson.databind.ObjectMapper "/>
+ <argument index="1" type="com.fasterxml.jackson.jaxrs.cfg.Annotations[]">
+ <array>
+ <value>JACKSON</value>
+ <value>JAXB</value>
+ </array>
+ </argument>
+ </bean>
+ <bean id="jaas-filter" class="org.apache.cxf.jaxrs.security.JAASAuthenticationFilter">
+ <!-- Name of the JAAS Context -->
+ <property name="contextName" value="karaf"/>
+ <!-- Hint to the filter on how to have Principals representing users and roles separated
+ while initializing a SecurityContext -->
+ <property name="rolePrefix" value="ROLE_"/>
+
+ <property name="realmName" value="cxs"/>
+ <!-- Activate this if you want to force a redirect if auth is missing, by default it will trigger a 403 which
+ is usually preferred -->
+ <!--property name="redirectURI" value="/login.jsp"/-->
+ </bean>
+
+ <jaxrs:server address="/sfdc" id="restSFDCService">
+ <jaxrs:providers>
+ <ref component-id="jaxb-provider"/>
+ <ref component-id="cors-filter"/>
+ <ref component-id="jaas-filter"/>
+ </jaxrs:providers>
+
+ <jaxrs:serviceBeans>
+ <ref component-id="sfdcServiceEndPoint"/>
+ </jaxrs:serviceBeans>
+ </jaxrs:server>
+
+ <reference id="sfdcService" interface="org.apache.unomi.sfdc.services.SFDCService"/>
+
+ <bean id="sfdcServiceEndPoint" class="org.apache.unomi.sfdc.rest.SFDCEndPoint">
+ <property name="sfdcService" ref="sfdcService"/>
+ <property name="bundleContext" ref="blueprintBundleContext" />
+ </bean>
+</blueprint>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/pom.xml
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/pom.xml b/extensions/salesforce-connector/services/pom.xml
new file mode 100644
index 0000000..6508f58
--- /dev/null
+++ b/extensions/salesforce-connector/services/pom.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-salesforce-connector</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>unomi-salesforce-connector-services</artifactId>
+ <name>Apache Unomi :: Extensions :: Salesforce connector :: Service</name>
+ <description>Service implementation for the Apache Unomi Context Server extension that integrates with Salesforce</description>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-api</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.unomi</groupId>
+ <artifactId>unomi-persistence-spi</artifactId>
+ <version>1.2.0-incubating-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-security-cors</artifactId>
+ <version>${cxf.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient-osgi</artifactId>
+ <version>4.5.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <version>7.4.4.v20110707</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>7.4.4.v20110707</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ <version>7.4.4.v20110707</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>7.4.4.v20110707</version>
+ </dependency>
+ <dependency>
+ <groupId>org.cometd.java</groupId>
+ <artifactId>bayeux-api</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.cometd.java</groupId>
+ <artifactId>cometd-java-common</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.cometd.java</groupId>
+ <artifactId>cometd-java-client</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Unit tests -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.6.6</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
+ <Import-Package>
+ sun.misc;resolution:=optional,
+ net.sf.ehcache;resolution:=optional,
+ net.spy.memcached;resolution:=optional,
+ org.ietf.jgss;resolution:=optional,
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>
+ src/main/resources/org.apache.unomi.sfdc.cfg
+ </file>
+ <type>cfg</type>
+ <classifier>sfdccfg</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/FieldMapping.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/FieldMapping.java b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/FieldMapping.java
new file mode 100644
index 0000000..68420ae
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/FieldMapping.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.unomi.sfdc.services;
+
+/**
+ * A mapping between two fields
+ */
+public class FieldMapping {
+
+ public enum MappingDirection {
+ LEFT_TO_RIGHT,
+ RIGHT_TO_LEFT,
+ BIDIRECTIONAL
+ }
+
+ private MappedField leftField;
+ private MappedField rightField;
+ private String fieldConverterIdentifier;
+ private MappingDirection mappingDirection;
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/MappedField.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/MappedField.java b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/MappedField.java
new file mode 100644
index 0000000..872487d
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/MappedField.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.unomi.sfdc.services;
+
+/**
+ * A field used in a field mapping
+ */
+public class MappedField {
+
+ private String name;
+ private String type;
+ private boolean readOnly;
+ private String source;
+ private boolean identifier;
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCConfiguration.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCConfiguration.java b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCConfiguration.java
new file mode 100644
index 0000000..fc84579
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCConfiguration.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.unomi.sfdc.services;
+
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.unomi.api.Item;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An persistence item that contains the configuration to the Salesforce service.
+ */
+public class SFDCConfiguration extends Item {
+
+ /**
+ * The ImportConfiguration ITEM_TYPE
+ *
+ * @see Item for a discussion of ITEM_TYPE
+ */
+ public static final String ITEM_TYPE = "sfdcConfiguration";
+
+ private String sfdcLoginEndpoint;
+ private String sfdcUserUsername;
+ private String sfdcUserPassword;
+ private String sfdcUserSecurityToken;
+ private String sfdcConsumerKey;
+ private String sfdcConsumerSecret;
+ private String sfdcChannel;
+ private String sfdcFieldMappings;
+ private String sfdcFieldMappingsIdentifier;
+ private long sfdcSessionTimeout = 15 * 60 * 1000L; // 15 minutes by default
+
+ private Map<String, String> unomiToSfdcFieldMappings = new HashMap<>();
+ private Map<String, String> sfdcToUnomiFieldMappings = new HashMap<>();
+
+ private String unomiIdentifierField;
+ private String sfdcIdentifierField;
+
+ public SFDCConfiguration() {
+ }
+
+ public String getSfdcLoginEndpoint() {
+ return sfdcLoginEndpoint;
+ }
+
+ public void setSfdcLoginEndpoint(String sfdcLoginEndpoint) {
+ this.sfdcLoginEndpoint = sfdcLoginEndpoint;
+ }
+
+ public String getSfdcUserUsername() {
+ return sfdcUserUsername;
+ }
+
+ public void setSfdcUserUsername(String sfdcUserUsername) {
+ this.sfdcUserUsername = sfdcUserUsername;
+ }
+
+ public String getSfdcUserPassword() {
+ return sfdcUserPassword;
+ }
+
+ public void setSfdcUserPassword(String sfdcUserPassword) {
+ this.sfdcUserPassword = sfdcUserPassword;
+ }
+
+ public String getSfdcUserSecurityToken() {
+ return sfdcUserSecurityToken;
+ }
+
+ public void setSfdcUserSecurityToken(String sfdcUserSecurityToken) {
+ this.sfdcUserSecurityToken = sfdcUserSecurityToken;
+ }
+
+ public String getSfdcConsumerKey() {
+ return sfdcConsumerKey;
+ }
+
+ public void setSfdcConsumerKey(String sfdcConsumerKey) {
+ this.sfdcConsumerKey = sfdcConsumerKey;
+ }
+
+ public String getSfdcConsumerSecret() {
+ return sfdcConsumerSecret;
+ }
+
+ public void setSfdcConsumerSecret(String sfdcConsumerSecret) {
+ this.sfdcConsumerSecret = sfdcConsumerSecret;
+ }
+
+ public String getSfdcChannel() {
+ return sfdcChannel;
+ }
+
+ public void setSfdcChannel(String sfdcChannel) {
+ this.sfdcChannel = sfdcChannel;
+ }
+
+ public String getSfdcFieldMappings() {
+ return sfdcFieldMappings;
+ }
+
+ public String getSfdcFieldMappingsIdentifier() {
+ return sfdcFieldMappingsIdentifier;
+ }
+
+ public long getSfdcSessionTimeout() {
+ return sfdcSessionTimeout;
+ }
+
+ public void setSfdcSessionTimeout(long sfdcSessionTimeout) {
+ this.sfdcSessionTimeout = sfdcSessionTimeout;
+ }
+
+ public void setSfdcFieldMappings(String sfdcFieldMappings) {
+ this.sfdcFieldMappings = sfdcFieldMappings;
+ String[] mappings = sfdcFieldMappings.split(",");
+ if (mappings != null && mappings.length > 0) {
+ for (String mapping : mappings) {
+ String[] parts = mapping.split("=");
+ if (parts != null && parts.length == 2) {
+ unomiToSfdcFieldMappings.put(parts[0], parts[1]);
+ sfdcToUnomiFieldMappings.put(parts[1], parts[0]);
+ }
+ }
+ }
+ }
+
+ public void setSfdcFieldMappingsIdentifier(String sfdcFieldMappingsIdentifier) {
+ this.sfdcFieldMappingsIdentifier = sfdcFieldMappingsIdentifier;
+ String[] sfdcFieldMappingsIdentifierParts = sfdcFieldMappingsIdentifier.split("=");
+ if (sfdcFieldMappingsIdentifierParts != null && sfdcFieldMappingsIdentifierParts.length == 2) {
+ unomiIdentifierField = sfdcFieldMappingsIdentifierParts[0];
+ sfdcIdentifierField = sfdcFieldMappingsIdentifierParts[1];
+ }
+ }
+
+ public Map<String, String> getUnomiToSfdcFieldMappings() {
+ return unomiToSfdcFieldMappings;
+ }
+
+ public Map<String, String> getSfdcToUnomiFieldMappings() {
+ return sfdcToUnomiFieldMappings;
+ }
+
+ public String getUnomiIdentifierField() {
+ return unomiIdentifierField;
+ }
+
+ public String getSfdcIdentifierField() {
+ return sfdcIdentifierField;
+ }
+
+ public boolean isComplete() {
+ return (!StringUtils.isEmpty(sfdcLoginEndpoint) &&
+ !StringUtils.isEmpty(sfdcUserUsername) &&
+ !StringUtils.isEmpty(sfdcUserPassword) &&
+ !StringUtils.isEmpty(sfdcUserSecurityToken) &&
+ !StringUtils.isEmpty(sfdcConsumerKey) &&
+ !StringUtils.isEmpty(sfdcConsumerSecret) &&
+ !StringUtils.isEmpty(sfdcFieldMappingsIdentifier));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCService.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCService.java b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCService.java
new file mode 100644
index 0000000..ab5041b
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCService.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.unomi.sfdc.services;
+
+import org.apache.http.HttpException;
+import org.apache.unomi.api.Profile;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Public interface for the Salesforce connector
+ */
+public interface SFDCService {
+
+ /**
+ * Load the configuration from the persistence service (if it exists)
+ * @return an instance of the configuration if it was found, null otherwise
+ */
+ SFDCConfiguration loadConfiguration();
+
+ /**
+ * Save a Salesforce configuration into the persistence service
+ * @param sfdcConfiguration the configuration to persist
+ * @return
+ */
+ boolean saveConfiguration(SFDCConfiguration sfdcConfiguration);
+
+ /**
+ * Login into Salesforce using the configuration passed in the methods arguments.
+ * @param sfdcConfiguration the configuration to use for the login
+ * @return true if the login was successful, false otherwise
+ * @throws HttpException
+ * @throws IOException
+ */
+ boolean login(SFDCConfiguration sfdcConfiguration) throws HttpException, IOException;
+
+ SFDCSession getSFDCSession();
+
+ void logout();
+
+ /**
+ * Create or update a lead based on a Unomi profile.
+ * @param profile
+ * @return a String containing the identifier of the corresponding SFDC lead
+ */
+ String createOrUpdateLead(Profile profile);
+
+ /**
+ * Updates a Unomi profile from a Salesforce lead
+ * @param profile
+ * @return true if the profile was updated, false otherwise.
+ */
+ boolean updateProfileFromLead(Profile profile);
+
+ Set<String> getRecentLeadIds();
+
+ Map<String,Object> getLead(String leadId);
+
+ Map<String,Object> query(String query);
+
+ Set<String> findLeadIdsByIdentifierValue(String identifierFieldValue);
+
+ Map<String,Object> getLimits();
+}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a897ab84/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCSession.java
----------------------------------------------------------------------
diff --git a/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCSession.java b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCSession.java
new file mode 100644
index 0000000..d8938b3
--- /dev/null
+++ b/extensions/salesforce-connector/services/src/main/java/org/apache/unomi/sfdc/services/SFDCSession.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.unomi.sfdc.services;
+
+/**
+ * Model object that stores Salesforce session data
+ */
+public class SFDCSession {
+ private String sessionId;
+ private String endPoint;
+ private String signature;
+ private String id;
+ private String tokenType;
+ private Long issuedAt;
+ private Long timeout;
+
+ public SFDCSession(String sessionId, String endPoint, String signature, String id, String tokenType, String issuedAt, Long timeout) {
+ this.sessionId = sessionId;
+ this.endPoint = endPoint;
+ this.signature = signature;
+ this.id = id;
+ this.tokenType = tokenType;
+ this.issuedAt = Long.parseLong(issuedAt) * 1000; // value in in seconds, we convert it to milliseconds
+ this.timeout = timeout;
+ }
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public String getEndPoint() {
+ return endPoint;
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getTokenType() {
+ return tokenType;
+ }
+
+ public Long getIssuedAt() {
+ return issuedAt;
+ }
+
+ public boolean isExpired() {
+ if (System.currentTimeMillis() < this.issuedAt + this.timeout) {
+ return false;
+ }
+ return true;
+ }
+}