/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed 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.springframework.integration.x.twitter;
import groovy.json.JsonSlurper;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.social.support.URIBuilder;
import org.springframework.social.twitter.api.impl.TwitterTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Message producer which reads form Twitter's public search endpoints.
* Reads from <a href="https://api.twitter.com/1.1/search/tweets.json"/>
* The available parameters map directly to those defined for the
* <a href="https://dev.twitter.com/docs/api/1.1/get/search/tweets">public search</a> API.
*
* @author Mark Fisher
* @author Luke Taylor
* @author David Turanski
*/
public class TwitterSearchChannelAdapter extends AbstractTwitterInboundChannelAdapter {
public static enum ResultType {
mixed, recent, popular
};
private static final String API_URL_BASE = "https://api.twitter.com/1.1/search/tweets.json";
private String query;
private String geocode;
private ResultType resultType = ResultType.mixed;
private Long sinceId;
private boolean includeEntities = true;
public TwitterSearchChannelAdapter(TwitterTemplate twitter) {
super(twitter);
}
public void setQuery(String query) {
this.query = query;
}
public void setGeocode(String geocode) {
this.geocode = geocode;
}
public void setResultType(ResultType resultType) {
this.resultType = resultType;
}
public void setIncludeEntities(boolean includeEntities) {
this.includeEntities = includeEntities;
}
@Override
public String getComponentType() {
return "twitter:search-channel-adapter";
}
@Override
protected URI buildUri() {
URIBuilder b = URIBuilder.fromUri(API_URL_BASE);
b.queryParam("q", query);
if (!includeEntities) {
b.queryParam("include_entities", "false");
}
if (!resultType.equals(ResultType.mixed)) {
b.queryParam("result_type", resultType.toString());
}
if (StringUtils.hasText(getLanguage())) {
b.queryParam("lang", getLanguage());
}
if (StringUtils.hasText(geocode)) {
b.queryParam("geocode", geocode);
}
if (sinceId != null) {
b.queryParam("since_id", String.valueOf(sinceId));
}
URI uri = b.build();
if (logger.isDebugEnabled()) {
logger.debug("Search uri:" + uri);
}
return uri;
}
/*
* Unwrap and split the search results into individual tweets. Advance the cursor ("since_id") for the next query.
* To avoid rate limit errors, wait at least 2 seconds (maximum rate according to twitter is 450/15 min or once every 2 seconds).
* If no data
* is returned, wait 10 seconds.
*/
@Override
protected void doSendLine(String line) {
ObjectMapper mapper = new ObjectMapper();
JsonSlurper jsonSlurper = new JsonSlurper();
@SuppressWarnings("unchecked")
Map<String, List<Map<String, ?>>> map = (Map<String, List<Map<String, ?>>>) jsonSlurper.parseText(line);
List<Map<String, ?>> statuses = map.get("statuses");
for (Map<String, ?> tweet : statuses) {
StringWriter sw = new StringWriter();
try {
mapper.writeValue(sw, tweet);
sendMessage(MessageBuilder.withPayload(sw.toString()).build());
}
catch (JsonGenerationException ex) {
logger.error("Failed to convert tweet to json: " + ex.getMessage());
break;
}
catch (JsonMappingException ex) {
logger.error("Failed to convert tweet to json: " + ex.getMessage());
break;
}
catch (IOException ex) {
logger.error("Failed to convert tweet to json: " + ex.getMessage());
break;
}
}
if (!CollectionUtils.isEmpty(statuses)) {
this.sinceId = ((Long) (statuses.get(statuses.size() - 1).get("id")));
wait(2100);
}
else {
wait(10000);
}
}
}