/**
* 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.camel.component.salesforce.internal.processor;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.TypeConverter;
import org.apache.camel.component.salesforce.NotFoundBehaviour;
import org.apache.camel.component.salesforce.SalesforceEndpoint;
import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
import org.apache.camel.component.salesforce.api.SalesforceException;
import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
import org.apache.camel.component.salesforce.api.dto.approval.ApprovalRequest;
import org.apache.camel.component.salesforce.api.dto.approval.ApprovalRequests;
import org.apache.camel.component.salesforce.internal.PayloadFormat;
import org.apache.camel.component.salesforce.internal.client.DefaultRestClient;
import org.apache.camel.component.salesforce.internal.client.RestClient;
import org.apache.camel.util.ServiceHelper;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.APEX_METHOD;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.APEX_QUERY_PARAM_PREFIX;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.APEX_URL;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.API_VERSION;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_BLOB_FIELD_NAME;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_CLASS;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_EXT_ID_NAME;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_EXT_ID_VALUE;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_FIELDS;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_ID;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_NAME;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_QUERY;
import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_SEARCH;
public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor {
protected static final String RESPONSE_CLASS = AbstractRestProcessor.class.getName() + ".responseClass";
private static final Pattern URL_TEMPLATE = Pattern.compile("\\{([^\\{\\}]+)\\}");
private RestClient restClient;
private Map<String, Class<?>> classMap;
private final NotFoundBehaviour notFoundBehaviour;
public AbstractRestProcessor(SalesforceEndpoint endpoint) throws SalesforceException {
super(endpoint);
final SalesforceEndpointConfig configuration = endpoint.getConfiguration();
final PayloadFormat payloadFormat = configuration.getFormat();
notFoundBehaviour = configuration.getNotFoundBehaviour();
this.restClient = new DefaultRestClient(httpClient, (String) endpointConfigMap.get(API_VERSION),
payloadFormat, session);
this.classMap = endpoint.getComponent().getClassMap();
}
// used in unit tests
AbstractRestProcessor(final SalesforceEndpoint endpoint, final RestClient restClient,
final Map<String, Class<?>> classMap) {
super(endpoint);
this.restClient = restClient;
this.classMap = classMap;
final SalesforceEndpointConfig configuration = endpoint.getConfiguration();
notFoundBehaviour = configuration.getNotFoundBehaviour();
}
@Override
public void start() throws Exception {
ServiceHelper.startService(restClient);
}
@Override
public void stop() throws Exception {
ServiceHelper.stopService(restClient);
}
@Override
public final boolean process(final Exchange exchange, final AsyncCallback callback) {
// pre-process request message
try {
processRequest(exchange);
} catch (SalesforceException e) {
exchange.setException(e);
callback.done(true);
return true;
} catch (RuntimeException e) {
exchange.setException(new SalesforceException(e.getMessage(), e));
callback.done(true);
return true;
}
// call Salesforce asynchronously
try {
// call Operation using REST client
switch (operationName) {
case GET_VERSIONS:
processGetVersions(exchange, callback);
break;
case GET_RESOURCES:
processGetResources(exchange, callback);
break;
case GET_GLOBAL_OBJECTS:
processGetGlobalObjects(exchange, callback);
break;
case GET_BASIC_INFO:
processGetBasicInfo(exchange, callback);
break;
case GET_DESCRIPTION:
processGetDescription(exchange, callback);
break;
case GET_SOBJECT:
processGetSobject(exchange, callback);
break;
case CREATE_SOBJECT:
processCreateSobject(exchange, callback);
break;
case UPDATE_SOBJECT:
processUpdateSobject(exchange, callback);
break;
case DELETE_SOBJECT:
processDeleteSobject(exchange, callback);
break;
case GET_SOBJECT_WITH_ID:
processGetSobjectWithId(exchange, callback);
break;
case UPSERT_SOBJECT:
processUpsertSobject(exchange, callback);
break;
case DELETE_SOBJECT_WITH_ID:
processDeleteSobjectWithId(exchange, callback);
break;
case GET_BLOB_FIELD:
processGetBlobField(exchange, callback);
break;
case QUERY:
processQuery(exchange, callback);
break;
case QUERY_MORE:
processQueryMore(exchange, callback);
break;
case QUERY_ALL:
processQueryAll(exchange, callback);
break;
case SEARCH:
processSearch(exchange, callback);
break;
case APEX_CALL:
processApexCall(exchange, callback);
break;
case RECENT:
processRecent(exchange, callback);
break;
case LIMITS:
processLimits(exchange, callback);
break;
case APPROVAL:
processApproval(exchange, callback);
break;
case APPROVALS:
processApprovals(exchange, callback);
break;
default:
throw new SalesforceException("Unknown operation name: " + operationName.value(), null);
}
} catch (SalesforceException e) {
exchange.setException(new SalesforceException(
String.format("Error processing %s: [%s] \"%s\"",
operationName.value(), e.getStatusCode(), e.getMessage()),
e));
callback.done(true);
return true;
} catch (RuntimeException e) {
exchange.setException(new SalesforceException(
String.format("Unexpected Error processing %s: \"%s\"",
operationName.value(), e.getMessage()),
e));
callback.done(true);
return true;
}
// continue routing asynchronously
return false;
}
final void processApproval(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
final TypeConverter converter = exchange.getContext().getTypeConverter();
final ApprovalRequest approvalRequestFromHeader = getParameter(SalesforceEndpointConfig.APPROVAL, exchange,
IGNORE_BODY, IS_OPTIONAL, ApprovalRequest.class);
final boolean requestGivenInHeader = approvalRequestFromHeader != null;
// find if there is a ApprovalRequest as `approval` in the message header
final ApprovalRequest approvalHeader = Optional.ofNullable(approvalRequestFromHeader)
.orElse(new ApprovalRequest());
final Message incomingMessage = exchange.getIn();
final Map<String, Object> incomingHeaders = incomingMessage.getHeaders();
final boolean requestGivenInParametersInHeader = processApprovalHeaderValues(approvalHeader, incomingHeaders);
final boolean nothingInheader = !requestGivenInHeader && !requestGivenInParametersInHeader;
final Object approvalBody = incomingMessage.getBody();
final boolean bodyIsIterable = approvalBody instanceof Iterable;
final boolean bodyIsIterableButEmpty = bodyIsIterable && !((Iterable) approvalBody).iterator().hasNext();
// body contains nothing of interest if it's null, holds an empty iterable or cannot be converted to
// ApprovalRequest
final boolean nothingInBody = !(approvalBody != null && !bodyIsIterableButEmpty);
// we found nothing in the headers or the body
if (nothingInheader && nothingInBody) {
throw new SalesforceException("Missing " + SalesforceEndpointConfig.APPROVAL
+ " parameter in header or ApprovalRequest or List of ApprovalRequests body", 0);
}
// let's try to resolve the request body to send
final ApprovalRequests requestsBody;
if (nothingInBody) {
// nothing in body use the header values only
requestsBody = new ApprovalRequests(approvalHeader);
} else if (bodyIsIterable) {
// multiple ApprovalRequests are found
final Iterable<?> approvalRequests = (Iterable<?>) approvalBody;
// use header values as template and apply them to the body
final List<ApprovalRequest> requests = StreamSupport.stream(approvalRequests.spliterator(), false)
.map(value -> converter.convertTo(ApprovalRequest.class, value))
.map(request -> request.applyTemplate(approvalHeader)).collect(Collectors.toList());
requestsBody = new ApprovalRequests(requests);
} else {
// we've looked at the body, and are expecting to see something resembling ApprovalRequest in there
// but lets see if that is so
final ApprovalRequest given = converter.tryConvertTo(ApprovalRequest.class, approvalBody);
final ApprovalRequest request = Optional.ofNullable(given).orElse(new ApprovalRequest())
.applyTemplate(approvalHeader);
requestsBody = new ApprovalRequests(request);
}
final InputStream request = getRequestStream(requestsBody);
restClient.approval(request, (response, exception) -> processResponse(exchange, response, exception, callback));
}
final boolean processApprovalHeaderValues(final ApprovalRequest approvalRequest,
final Map<String, Object> incomingHeaderValues) {
// loop trough all header values, find those that start with `approval.`
// set the property value to the given approvalRequest and return if
// any value was set
return incomingHeaderValues.entrySet().stream().filter(kv -> kv.getKey().startsWith("approval.")).map(kv -> {
final String property = kv.getKey().substring(9);
Object value = kv.getValue();
if (value != null) {
try {
setPropertyValue(approvalRequest, property, value);
return true;
} catch (SalesforceException e) {
throw new IllegalArgumentException(e);
}
}
return false;
}).reduce(false, (a, b) -> a || b);
}
private void processApprovals(final Exchange exchange, final AsyncCallback callback) {
restClient.approvals((response, exception) -> processResponse(exchange, response, exception, callback));
}
private void processGetVersions(final Exchange exchange, final AsyncCallback callback) {
restClient.getVersions(new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
// process response entity and create out message
processResponse(exchange, response, exception, callback);
}
});
}
private void processGetResources(final Exchange exchange, final AsyncCallback callback) {
restClient.getResources(new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processGetGlobalObjects(final Exchange exchange, final AsyncCallback callback) {
restClient.getGlobalObjects(new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processGetBasicInfo(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName = getParameter(SOBJECT_NAME, exchange, USE_BODY, NOT_OPTIONAL);
restClient.getBasicInfo(sObjectName, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processGetDescription(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
sObjectName = getParameter(SOBJECT_NAME, exchange, USE_BODY, NOT_OPTIONAL);
restClient.getDescription(sObjectName, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processGetSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
String sObjectIdValue;
// determine parameters from input AbstractSObject
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
sObjectIdValue = sObjectBase.getId();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectIdValue = getParameter(SOBJECT_ID, exchange, USE_BODY, NOT_OPTIONAL);
}
final String sObjectId = sObjectIdValue;
// use sObject name to load class
setResponseClass(exchange, sObjectName);
// get optional field list
String fieldsValue = getParameter(SOBJECT_FIELDS, exchange, IGNORE_BODY, IS_OPTIONAL);
String[] fields = null;
if (fieldsValue != null) {
fields = fieldsValue.split(",");
}
restClient.getSObject(sObjectName, sObjectId, fields, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, sObjectId, null, null);
}
});
}
private void processCreateSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
// determine parameters from input AbstractSObject
AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
}
restClient.createSObject(sObjectName, getRequestStream(exchange),
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processUpdateSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
// determine parameters from input AbstractSObject
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
String sObjectId;
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
// remember the sObject Id
sObjectId = sObjectBase.getId();
// clear base object fields, which cannot be updated
sObjectBase.clearBaseFields();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectId = getParameter(SOBJECT_ID, exchange, IGNORE_BODY, NOT_OPTIONAL);
}
final String finalsObjectId = sObjectId;
restClient.updateSObject(sObjectName, sObjectId, getRequestStream(exchange),
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, finalsObjectId, null, null);
}
});
}
private void processDeleteSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
// determine parameters from input AbstractSObject
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
String sObjectIdValue;
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
sObjectIdValue = sObjectBase.getId();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectIdValue = getParameter(SOBJECT_ID, exchange, USE_BODY, NOT_OPTIONAL);
}
final String sObjectId = sObjectIdValue;
restClient.deleteSObject(sObjectName, sObjectId, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, sObjectId, null, null);
}
});
}
private void processGetSobjectWithId(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
Object oldValue = null;
String sObjectExtIdValue;
final String sObjectExtIdName = getParameter(SOBJECT_EXT_ID_NAME,
exchange, IGNORE_BODY, NOT_OPTIONAL);
// determine parameters from input AbstractSObject
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
oldValue = getAndClearPropertyValue(sObjectBase, sObjectExtIdName);
sObjectExtIdValue = oldValue.toString();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectExtIdValue = getParameter(SOBJECT_EXT_ID_VALUE, exchange, USE_BODY, NOT_OPTIONAL);
}
// use sObject name to load class
setResponseClass(exchange, sObjectName);
final Object finalOldValue = oldValue;
restClient.getSObjectWithId(sObjectName, sObjectExtIdName, sObjectExtIdValue,
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
}
});
}
private void processUpsertSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
String sObjectExtIdValue;
final String sObjectExtIdName = getParameter(SOBJECT_EXT_ID_NAME, exchange,
IGNORE_BODY, NOT_OPTIONAL);
// determine parameters from input AbstractSObject
Object oldValue = null;
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
oldValue = getAndClearPropertyValue(sObjectBase, sObjectExtIdName);
sObjectExtIdValue = oldValue.toString();
// clear base object fields, which cannot be updated
sObjectBase.clearBaseFields();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectExtIdValue = getParameter(SOBJECT_EXT_ID_VALUE, exchange, IGNORE_BODY, NOT_OPTIONAL);
}
final Object finalOldValue = oldValue;
restClient.upsertSObject(sObjectName, sObjectExtIdName, sObjectExtIdValue, getRequestStream(exchange),
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
}
});
}
private void processDeleteSobjectWithId(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
final String sObjectExtIdName = getParameter(SOBJECT_EXT_ID_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
// determine parameters from input AbstractSObject
Object oldValue = null;
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
String sObjectExtIdValue;
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
oldValue = getAndClearPropertyValue(sObjectBase, sObjectExtIdName);
sObjectExtIdValue = oldValue.toString();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectExtIdValue = getParameter(SOBJECT_EXT_ID_VALUE, exchange, USE_BODY, NOT_OPTIONAL);
}
final Object finalOldValue = oldValue;
restClient.deleteSObjectWithId(sObjectName, sObjectExtIdName, sObjectExtIdValue,
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
}
});
}
private void processGetBlobField(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
String sObjectName;
// get blob field name
final String sObjectBlobFieldName = getParameter(SOBJECT_BLOB_FIELD_NAME,
exchange, IGNORE_BODY, NOT_OPTIONAL);
// determine parameters from input AbstractSObject
final AbstractSObjectBase sObjectBase = exchange.getIn().getBody(AbstractSObjectBase.class);
String sObjectIdValue;
if (sObjectBase != null) {
sObjectName = sObjectBase.getClass().getSimpleName();
sObjectIdValue = sObjectBase.getId();
} else {
sObjectName = getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, NOT_OPTIONAL);
sObjectIdValue = getParameter(SOBJECT_ID, exchange, USE_BODY, NOT_OPTIONAL);
}
final String sObjectId = sObjectIdValue;
restClient.getBlobField(sObjectName, sObjectId, sObjectBlobFieldName,
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
restoreFields(exchange, sObjectBase, sObjectId, null, null);
}
});
}
private void processQuery(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
final String sObjectQuery = getParameter(SOBJECT_QUERY, exchange, USE_BODY, NOT_OPTIONAL);
// use custom response class property
setResponseClass(exchange, null);
restClient.query(sObjectQuery, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processQueryMore(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
// reuse SOBJECT_QUERY parameter name for nextRecordsUrl
final String nextRecordsUrl = getParameter(SOBJECT_QUERY, exchange, USE_BODY, NOT_OPTIONAL);
// use custom response class property
setResponseClass(exchange, null);
restClient.queryMore(nextRecordsUrl, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processQueryAll(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
final String sObjectQuery = getParameter(SOBJECT_QUERY, exchange, USE_BODY, NOT_OPTIONAL);
// use custom response class property
setResponseClass(exchange, null);
restClient.queryAll(sObjectQuery, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processSearch(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
final String sObjectSearch = getParameter(SOBJECT_SEARCH, exchange, USE_BODY, NOT_OPTIONAL);
restClient.search(sObjectSearch, new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private void processApexCall(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
// HTTP method, URL and query params for APEX call
final String apexUrl = getApexUrl(exchange);
String apexMethod = getParameter(APEX_METHOD, exchange, IGNORE_BODY, IS_OPTIONAL);
// default to GET
if (apexMethod == null) {
apexMethod = "GET";
log.debug("Using HTTP GET method by default for APEX REST call for {}", apexUrl);
}
final Map<String, Object> queryParams = getQueryParams(exchange);
// set response class
setResponseClass(exchange, getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, IS_OPTIONAL));
// set request stream
final Object requestBody = exchange.getIn().getBody();
final InputStream requestDto =
(requestBody != null && !(requestBody instanceof Map)) ? getRequestStream(exchange) : null;
restClient.apexCall(apexMethod, apexUrl, queryParams, requestDto,
new RestClient.ResponseCallback() {
@Override
public void onResponse(InputStream response, SalesforceException exception) {
processResponse(exchange, response, exception, callback);
}
});
}
private String getApexUrl(Exchange exchange) throws SalesforceException {
final String apexUrl = getParameter(APEX_URL, exchange, IGNORE_BODY, NOT_OPTIONAL);
final Matcher matcher = URL_TEMPLATE.matcher(apexUrl);
StringBuilder result = new StringBuilder();
int start = 0;
while (matcher.find()) {
// append part before parameter template
result.append(apexUrl.substring(start, matcher.start()));
start = matcher.end();
// append template value from exchange header
final String parameterName = matcher.group(1);
final Object value = exchange.getIn().getHeader(parameterName);
if (value == null) {
throw new IllegalArgumentException("Missing APEX URL template header " + parameterName);
}
try {
result.append(URLEncoder.encode(String.valueOf(value), "UTF-8").replaceAll("\\+", "%20"));
} catch (UnsupportedEncodingException e) {
throw new SalesforceException("Unexpected error: " + e.getMessage(), e);
}
}
if (start != 0) {
// append remaining URL
result.append(apexUrl.substring(start));
final String resolvedUrl = result.toString();
log.debug("Resolved APEX URL {} to {}", apexUrl, resolvedUrl);
return resolvedUrl;
}
return apexUrl;
}
private void processRecent(Exchange exchange, AsyncCallback callback) throws SalesforceException {
final Integer limit = getParameter(SalesforceEndpointConfig.LIMIT, exchange, true, true, Integer.class);
restClient.recent(limit, (response, exception) -> processResponse(exchange, response, exception, callback));
}
private void processLimits(Exchange exchange, AsyncCallback callback) {
restClient.limits((response, exception) -> processResponse(exchange, response, exception, callback));
}
@SuppressWarnings("unchecked")
private Map<String, Object> getQueryParams(Exchange exchange) {
// use endpoint map
Map<String, Object> queryParams = new HashMap<String, Object>(endpoint.getConfiguration().getApexQueryParams());
// look for individual properties, allowing endpoint properties to be overridden
for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
if (entry.getKey().startsWith(APEX_QUERY_PARAM_PREFIX)) {
queryParams.put(entry.getKey().substring(APEX_QUERY_PARAM_PREFIX.length()), entry.getValue());
}
}
// add params from body if it's a map
final Object body = exchange.getIn().getBody();
if (body instanceof Map) {
queryParams.putAll((Map<String, Object>) body);
}
log.debug("Using APEX query params {}", queryParams);
return queryParams;
}
private void restoreFields(Exchange exchange, AbstractSObjectBase sObjectBase,
String sObjectId, String sObjectExtIdName, Object oldValue) {
// restore fields
if (sObjectBase != null) {
// restore the Id if it was cleared
if (sObjectId != null) {
sObjectBase.setId(sObjectId);
}
// restore the external id if it was cleared
if (sObjectExtIdName != null && oldValue != null) {
try {
setPropertyValue(sObjectBase, sObjectExtIdName, oldValue);
} catch (SalesforceException e) {
// YES, the exchange may fail if the property cannot be reset!!!
exchange.setException(e);
}
}
}
}
private void setPropertyValue(Object sObjectBase, String name, Object value) throws SalesforceException {
try {
// set the value with the set method
Method setMethod = sObjectBase.getClass().getMethod("set" + name, value.getClass());
setMethod.invoke(sObjectBase, value);
} catch (NoSuchMethodException e) {
throw new SalesforceException(
String.format("SObject %s does not have a field %s",
sObjectBase.getClass().getName(), name),
e);
} catch (InvocationTargetException e) {
throw new SalesforceException(
String.format("Error setting value %s.%s",
sObjectBase.getClass().getSimpleName(), name),
e);
} catch (IllegalAccessException e) {
throw new SalesforceException(
String.format("Error accessing value %s.%s",
sObjectBase.getClass().getSimpleName(), name),
e);
}
}
private Object getAndClearPropertyValue(AbstractSObjectBase sObjectBase, String propertyName) throws SalesforceException {
try {
// obtain the value using the get method
Method getMethod = sObjectBase.getClass().getMethod("get" + propertyName);
Object value = getMethod.invoke(sObjectBase);
// clear the value with the set method
Method setMethod = sObjectBase.getClass().getMethod("set" + propertyName, getMethod.getReturnType());
setMethod.invoke(sObjectBase, new Object[]{null});
return value;
} catch (NoSuchMethodException e) {
throw new SalesforceException(
String.format("SObject %s does not have a field %s",
sObjectBase.getClass().getSimpleName(), propertyName),
e);
} catch (InvocationTargetException e) {
throw new SalesforceException(
String.format("Error getting/setting value %s.%s",
sObjectBase.getClass().getSimpleName(), propertyName),
e);
} catch (IllegalAccessException e) {
throw new SalesforceException(
String.format("Error accessing value %s.%s",
sObjectBase.getClass().getSimpleName(), propertyName),
e);
}
}
// pre-process request message
protected abstract void processRequest(Exchange exchange) throws SalesforceException;
// get request stream from In message
protected abstract InputStream getRequestStream(Exchange exchange) throws SalesforceException;
/**
* Returns {@link InputStream} to serialized form of the given object.
*
* @param object
* object to serialize
* @return stream to read serialized object from
*/
protected abstract InputStream getRequestStream(Object object) throws SalesforceException;
private void setResponseClass(Exchange exchange, String sObjectName) throws SalesforceException {
Class<?> sObjectClass;
if (sObjectName != null) {
// lookup class from class map
sObjectClass = classMap.get(sObjectName);
if (null == sObjectClass) {
throw new SalesforceException(String.format("No class found for SObject %s", sObjectName), null);
}
} else {
// use custom response class property
final String className = getParameter(SOBJECT_CLASS, exchange, IGNORE_BODY, NOT_OPTIONAL);
try {
sObjectClass = endpoint.getComponent().getCamelContext()
.getClassResolver().resolveMandatoryClass(className);
} catch (ClassNotFoundException e) {
throw new SalesforceException(
String.format("SObject class not found %s, %s",
className, e.getMessage()),
e);
}
}
exchange.setProperty(RESPONSE_CLASS, sObjectClass);
}
// process response entity and set out message in exchange
protected abstract void processResponse(Exchange exchange, InputStream responseEntity, SalesforceException ex, AsyncCallback callback);
final boolean shouldReport(SalesforceException ex) {
return !(ex instanceof NoSuchSObjectException && notFoundBehaviour == NotFoundBehaviour.NULL);
}
}