/**
* 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;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeCamelException;
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.component.salesforce.internal.streaming.PushTopicHelper;
import org.apache.camel.component.salesforce.internal.streaming.SubscriptionHelper;
import org.apache.camel.impl.DefaultConsumer;
import org.apache.camel.util.ServiceHelper;
import org.codehaus.jackson.map.ObjectMapper;
import org.cometd.bayeux.Message;
import org.cometd.bayeux.client.ClientSessionChannel;
/**
* The Salesforce consumer.
*/
public class SalesforceConsumer extends DefaultConsumer {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String EVENT_PROPERTY = "event";
private static final String TYPE_PROPERTY = "type";
private static final String CREATED_DATE_PROPERTY = "createdDate";
private static final String SOBJECT_PROPERTY = "sobject";
private static final double MINIMUM_VERSION = 24.0;
private final SalesforceEndpoint endpoint;
private final SubscriptionHelper subscriptionHelper;
private final String topicName;
private final Class<?> sObjectClass;
private boolean subscribed;
public SalesforceConsumer(SalesforceEndpoint endpoint, Processor processor, SubscriptionHelper helper) {
super(endpoint, processor);
this.endpoint = endpoint;
// check minimum supported API version
if (Double.valueOf(endpoint.getConfiguration().getApiVersion()) < MINIMUM_VERSION) {
throw new IllegalArgumentException("Minimum supported API version for consumer endpoints is " + 24.0);
}
this.topicName = endpoint.getTopicName();
this.subscriptionHelper = helper;
// get sObjectClass to convert to
final String sObjectName = endpoint.getConfiguration().getSObjectName();
if (sObjectName != null) {
sObjectClass = endpoint.getComponent().getClassMap().get(sObjectName);
if (sObjectClass == null) {
throw new IllegalArgumentException(String.format("SObject Class not found for %s", sObjectName));
}
} else {
final String className = endpoint.getConfiguration().getSObjectClass();
if (className != null) {
sObjectClass = endpoint.getComponent().getCamelContext().getClassResolver().resolveClass(className);
if (sObjectClass == null) {
throw new IllegalArgumentException(String.format("SObject Class not found %s", className));
}
} else {
log.warn("Property sObjectName or sObjectClass NOT set, messages will be of type java.lang.Map");
sObjectClass = null;
}
}
}
@Override
protected void doStart() throws Exception {
super.doStart();
final SalesforceEndpointConfig config = endpoint.getConfiguration();
// is a query configured in the endpoint?
if (config.getSObjectQuery() != null) {
// Note that we don't lookup topic if the query is not specified
// create REST client for PushTopic operations
SalesforceComponent component = endpoint.getComponent();
RestClient restClient = new DefaultRestClient(component.getConfig().getHttpClient(),
endpoint.getConfiguration().getApiVersion(), PayloadFormat.JSON, component.getSession());
// don't forget to start the client
ServiceHelper.startService(restClient);
try {
PushTopicHelper helper = new PushTopicHelper(config, topicName, restClient);
helper.createOrUpdateTopic();
} finally {
// don't forget to stop the client
ServiceHelper.stopService(restClient);
}
}
// subscribe to topic
subscriptionHelper.subscribe(topicName, this);
subscribed = true;
}
@Override
protected void doStop() throws Exception {
super.doStop();
if (subscribed) {
subscribed = false;
// unsubscribe from topic
subscriptionHelper.unsubscribe(topicName, this);
}
}
public void processMessage(ClientSessionChannel channel, Message message) {
final Exchange exchange = endpoint.createExchange();
org.apache.camel.Message in = exchange.getIn();
setHeaders(in, message);
// get event data
// TODO do we need to add NPE checks for message/data.get***???
Map<String, Object> data = message.getDataAsMap();
@SuppressWarnings("unchecked") final
Map<String, Object> event = (Map<String, Object>) data.get(EVENT_PROPERTY);
final Object eventType = event.get(TYPE_PROPERTY);
Object createdDate = event.get(CREATED_DATE_PROPERTY);
if (log.isDebugEnabled()) {
log.debug(String.format("Received event %s on channel %s created on %s",
eventType, channel.getChannelId(), createdDate));
}
in.setHeader("CamelSalesforceEventType", eventType);
in.setHeader("CamelSalesforceCreatedDate", createdDate);
// get SObject
@SuppressWarnings("unchecked")
final Map<String, Object> sObject = (Map<String, Object>) data.get(SOBJECT_PROPERTY);
try {
final String sObjectString = OBJECT_MAPPER.writeValueAsString(sObject);
log.debug("Received SObject: {}", sObjectString);
if (sObjectClass == null) {
// return sobject map as exchange body
in.setBody(sObject);
} else {
// create the expected SObject
in.setBody(OBJECT_MAPPER.readValue(
new StringReader(sObjectString), sObjectClass));
}
} catch (IOException e) {
final String msg = String.format("Error parsing message [%s] from Topic %s: %s",
message, topicName, e.getMessage());
handleException(msg, new RuntimeCamelException(msg, e));
}
try {
getAsyncProcessor().process(exchange, new AsyncCallback() {
public void done(boolean doneSync) {
// noop
if (log.isTraceEnabled()) {
log.trace("Done processing event: {} {}", eventType.toString(),
doneSync ? "synchronously" : "asynchronously");
}
}
});
} catch (Exception e) {
handleException(String.format("Error processing %s: %s", exchange, e.getMessage()), e);
} finally {
Exception ex = exchange.getException();
if (ex != null) {
handleException(String.format("Unhandled exception: %s", ex.getMessage()), ex);
}
}
}
private void setHeaders(org.apache.camel.Message in, Message message) {
Map<String, Object> headers = new HashMap<String, Object>();
// set topic name
headers.put("CamelSalesforceTopicName", topicName);
// set message properties as headers
headers.put("CamelSalesforceChannel", message.getChannel());
headers.put("CamelSalesforceClientId", message.getClientId());
in.setHeaders(headers);
}
@Override
public void handleException(String message, Throwable t) {
super.handleException(message, t);
}
public String getTopicName() {
return topicName;
}
}