/**
* 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.facebook;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import facebook4j.Facebook;
import facebook4j.Reading;
import facebook4j.json.DataObjectFactory;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.component.facebook.data.FacebookMethodsType;
import org.apache.camel.component.facebook.data.FacebookMethodsTypeHelper.MatchType;
import org.apache.camel.component.facebook.data.FacebookPropertiesHelper;
import org.apache.camel.component.facebook.data.ReadingBuilder;
import org.apache.camel.impl.ScheduledPollConsumer;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.component.facebook.FacebookConstants.FACEBOOK_DATE_FORMAT;
import static org.apache.camel.component.facebook.FacebookConstants.READING_PREFIX;
import static org.apache.camel.component.facebook.FacebookConstants.READING_PROPERTY;
import static org.apache.camel.component.facebook.data.FacebookMethodsTypeHelper.filterMethods;
import static org.apache.camel.component.facebook.data.FacebookMethodsTypeHelper.getHighestPriorityMethod;
import static org.apache.camel.component.facebook.data.FacebookMethodsTypeHelper.getMissingProperties;
import static org.apache.camel.component.facebook.data.FacebookMethodsTypeHelper.invokeMethod;
/**
* The Facebook consumer.
*/
public class FacebookConsumer extends ScheduledPollConsumer {
private static final Logger LOG = LoggerFactory.getLogger(FacebookConsumer.class);
private static final String SINCE_PREFIX = "since=";
private final FacebookEndpoint endpoint;
private final FacebookMethodsType method;
private final Map<String, Object> endpointProperties;
private String sinceTime;
private String untilTime;
public FacebookConsumer(FacebookEndpoint endpoint, Processor processor) {
super(endpoint, processor);
this.endpoint = endpoint;
// determine the consumer method to invoke
this.method = findMethod();
// get endpoint properties in a map
final HashMap<String, Object> properties = new HashMap<String, Object>();
FacebookPropertiesHelper.getEndpointProperties(endpoint.getConfiguration(), properties);
// skip since and until fields?
final Reading reading = (Reading) properties.get(READING_PROPERTY);
if (reading != null) {
final String queryString = reading.toString();
if (queryString.contains(SINCE_PREFIX)) {
// use the user supplied value to start with
final int startIndex = queryString.indexOf(SINCE_PREFIX) + SINCE_PREFIX.length();
int endIndex = queryString.indexOf('&', startIndex);
if (endIndex == -1) {
// ignore the closing square bracket
endIndex = queryString.length() - 1;
}
final String strSince = queryString.substring(startIndex, endIndex);
try {
this.sinceTime = URLDecoder.decode(strSince, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeCamelException(String.format("Error decoding %s.since with value %s due to: %s", READING_PREFIX, strSince, e.getMessage()), e);
}
LOG.debug("Using supplied property {}since value {}", READING_PREFIX, this.sinceTime);
}
if (queryString.contains("until=")) {
LOG.debug("Overriding configured property {}until", READING_PREFIX);
}
}
this.endpointProperties = Collections.unmodifiableMap(properties);
}
@Override
public boolean isGreedy() {
// make this consumer not greedy to avoid making too many Facebook calls
return false;
}
private FacebookMethodsType findMethod() {
FacebookMethodsType result;
// find one that takes the largest subset of endpoint parameters
final Set<String> argNames = new HashSet<String>();
argNames.addAll(FacebookPropertiesHelper.getEndpointPropertyNames(endpoint.getConfiguration()));
// add reading property for polling, if it doesn't already exist!
argNames.add(READING_PROPERTY);
final String[] argNamesArray = argNames.toArray(new String[argNames.size()]);
List<FacebookMethodsType> filteredMethods = filterMethods(
endpoint.getCandidates(), MatchType.SUPER_SET, argNamesArray);
if (filteredMethods.isEmpty()) {
throw new IllegalArgumentException(
String.format("Missing properties for %s, need one or more from %s",
endpoint.getMethod(),
getMissingProperties(endpoint.getMethod(), endpoint.getNameStyle(), argNames)));
} else if (filteredMethods.size() == 1) {
// single match
result = filteredMethods.get(0);
} else {
result = getHighestPriorityMethod(filteredMethods);
LOG.warn("Using highest priority method {} from methods {}", method, filteredMethods);
}
return result;
}
@Override
protected int poll() throws Exception {
// invoke the consumer method
final Map<String, Object> args = getMethodArguments();
try {
// also check whether we need to get raw JSON
String rawJSON = null;
Object result;
if (endpoint.getConfiguration().getJsonStoreEnabled() == null
|| !endpoint.getConfiguration().getJsonStoreEnabled()) {
result = invokeMethod(endpoint.getConfiguration().getFacebook(),
method, args);
} else {
final Facebook facebook = endpoint.getConfiguration().getFacebook();
synchronized (facebook) {
result = invokeMethod(facebook, method, args);
rawJSON = DataObjectFactory.getRawJSON(result);
}
}
// process result according to type
if (result != null && (result instanceof Collection || result.getClass().isArray())) {
// create an exchange for every element
final Object array = getResultAsArray(result);
final int length = Array.getLength(array);
for (int i = 0; i < length; i++) {
processResult(Array.get(array, i), rawJSON);
}
return length;
} else {
processResult(result, rawJSON);
return 1; // number of messages polled
}
} catch (Throwable t) {
throw ObjectHelper.wrapRuntimeCamelException(t);
}
}
private void processResult(Object result, String rawJSON) throws Exception {
Exchange exchange = endpoint.createExchange();
exchange.getIn().setBody(result);
if (rawJSON != null) {
exchange.getIn().setHeader(FacebookConstants.RAW_JSON_HEADER, rawJSON);
}
try {
// send message to next processor in the route
getProcessor().process(exchange);
} finally {
// log exception if an exception occurred and was not handled
if (exchange.getException() != null) {
getExceptionHandler().handleException("Error processing exchange", exchange, exchange.getException());
}
}
}
private Object getResultAsArray(Object result) {
if (result.getClass().isArray()) {
// no conversion needed
return result;
}
// must be a Collection
// TODO add support for Paging using ResponseList
Collection<?> collection = (Collection<?>) result;
return collection.toArray(new Object[collection.size()]);
}
private Map<String, Object> getMethodArguments() {
// start by setting the Reading since and until fields,
// these are used to avoid reading duplicate results across polls
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.putAll(endpointProperties);
Reading reading = (Reading) arguments.remove(READING_PROPERTY);
if (reading == null) {
reading = new Reading();
} else {
try {
reading = ReadingBuilder.copy(reading, true);
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException(String.format("Error creating property [%s]: %s",
READING_PROPERTY, e.getMessage()), e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(String.format("Error creating property [%s]: %s",
READING_PROPERTY, e.getMessage()), e);
}
}
// now set since and until for this poll
final SimpleDateFormat dateFormat = new SimpleDateFormat(FACEBOOK_DATE_FORMAT);
final long currentMillis = System.currentTimeMillis();
if (this.sinceTime == null) {
// first poll, set this to (current time - initial poll delay)
final Date startTime = new Date(currentMillis
- TimeUnit.MILLISECONDS.convert(getInitialDelay(), getTimeUnit()));
this.sinceTime = dateFormat.format(startTime);
} else if (this.untilTime != null) {
// use the last 'until' time
this.sinceTime = this.untilTime;
}
this.untilTime = dateFormat.format(new Date(currentMillis));
reading.since(this.sinceTime);
reading.until(this.untilTime);
arguments.put(READING_PROPERTY, reading);
return arguments;
}
}