/**
* Copyright 2014 Lockheed Martin Corporation
*
* 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 streamflow.spout.twitter;
import backtype.storm.Config;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichSpout;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
import backtype.storm.utils.Utils;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import streamflow.annotations.Component;
import streamflow.annotations.ComponentOutputs;
import streamflow.annotations.ComponentProperty;
import streamflow.annotations.Description;
import streamflow.annotations.ComponentInterface;
import twitter4j.*;
import twitter4j.conf.ConfigurationBuilder;
@Component(label = "Twitter Sample Spout", name = "twitter-sample-spout", type = "storm-spout", icon="icons/twitter.png")
@Description("Spouts and Bolts supporting Twitter functionality")
@ComponentOutputs({@ComponentInterface(key = "default", description = "Twitter Status")})
public class TwitterSampleSpout extends BaseRichSpout {
private SpoutOutputCollector collector;
private Logger logger;
private String consumerKey;
private String consumerSecret;
private String accessToken;
private String accessTokenSecret;
private String proxyHost;
private int proxyPort;
private final LinkedBlockingQueue<Status> queue = new LinkedBlockingQueue<Status>(100000);
private TwitterStream twitterStream;
@ComponentProperty(label = "OAuth Consumer Key", name = "oauth-consumer-key", required = true, type = "text", defaultValue = "")
@Description("Twitter OAuth Consumer Key")
@Inject
public void setConsumerKey(@Named("oauth-consumer-key") String consumerKey) {
this.consumerKey = consumerKey;
}
@ComponentProperty(label = "OAuth Consumer Secret", name = "oauth-consumer-secret", required = true, type = "text", defaultValue = "")
@Description("Twitter OAuth Consumer Secret")
@Inject
public void setConsumerSecret(@Named("oauth-consumer-secret") String consumerSecret) {
this.consumerSecret = consumerSecret;
}
@ComponentProperty(label = "OAuth Access Token", name = "oauth-access-token", required = true, type = "text", defaultValue = "")
@Description("Twitter OAuth Access Token")
@Inject
public void setAccessToken(@Named("oauth-access-token") String accessToken) {
this.accessToken = accessToken;
}
@ComponentProperty(label = "OAuth Access Token Secret", name = "oauth-access-token-secret", required = true, type = "text", defaultValue = "")
@Description("Twitter OAuth Access Token Secret")
@Inject
public void setAccessTokenSecret(@Named("oauth-access-token-secret") String accessTokenSecret) {
this.accessTokenSecret = accessTokenSecret;
}
@Inject
public void setLogger(Logger logger){
this.logger = logger;
}
// @Inject(optional = true)
public void setProxyHost(@Named("http.proxy.host") String proxyHost) {
this.proxyHost = proxyHost;
}
// @Inject(optional = true)
public void setProxyPort(@Named("http.proxy.port") int proxyPort) {
this.proxyPort = proxyPort;
}
@Override
public void open(Map config, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
logger.info("Twitter Sampler Started: Consumer Key = " + consumerKey
+ ", Consumer Secret = " + consumerSecret + ", Access Token = " + accessToken
+ ", Access Token Secret = " + accessTokenSecret);
if (StringUtils.isNotBlank(consumerKey) && StringUtils.isNotBlank(consumerSecret) &&
StringUtils.isNotBlank(accessToken) && StringUtils.isNotBlank(accessTokenSecret)) {
// Build the twitter config to authenticate the requests
ConfigurationBuilder twitterConfig = new ConfigurationBuilder()
.setOAuthConsumerKey(consumerKey)
.setOAuthConsumerSecret(consumerSecret)
.setOAuthAccessToken(accessToken)
.setOAuthAccessTokenSecret(accessTokenSecret)
.setJSONStoreEnabled(true)
.setIncludeEntitiesEnabled(true)
.setIncludeEntitiesEnabled(true);
// Add the proxy settings to the Twitter config if they were specified
if (StringUtils.isNotBlank(proxyHost) && proxyPort > 0) {
try {
twitterConfig.setHttpProxyPort(proxyPort).setHttpProxyHost(proxyHost);
}
catch (Exception ex) {
}
}
// Status listener which handle the status events and add them to the queue
StatusListener listener = new StatusListener() {
@Override
public void onStatus(Status status) {
queue.offer(status);
}
@Override
public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
logger.debug("Twitter Deletion Notice: " + statusDeletionNotice.getUserId());
}
@Override
public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
logger.debug("Twitter On Track Limitation Notice: Number Of Limited Statuses"
+ numberOfLimitedStatuses);
}
@Override
public void onScrubGeo(long userId, long upToStatusId) {
logger.debug("Twitter Scrub Geo: UserID = " + userId
+ ", UpToStatusId = " + upToStatusId);
}
@Override
public void onException(Exception exception) {
logger.debug("Twitter Exception: " + exception.getMessage());
}
@Override
public void onStallWarning(StallWarning stallWarning) {
logger.debug("Twitter Stall Warning: " + stallWarning.toString());
}
};
TwitterStreamFactory twitterFactory = new TwitterStreamFactory(twitterConfig.build());
twitterStream = twitterFactory.getInstance();
twitterStream.addListener(listener);
twitterStream.sample();
logger.info("Twitter Sample Stream Initialized");
} else {
logger.info("Twitter Sampler missing required OAuth properties. "
+ "Pleast check your settings and try again.");
}
}
@Override
public void nextTuple() {
Status status = queue.poll();
if (status == null) {
Utils.sleep(50);
} else {
// Emit the twitter status as a JSON String
collector.emit(new Values(status));
}
}
@Override
public void close() {
if (twitterStream != null) {
twitterStream.shutdown();
}
logger.info("Twitter Sampler Stopped");
}
@Override
public Map<String, Object> getComponentConfiguration() {
Config config = new Config();
config.setMaxTaskParallelism(1);
return config;
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("tweet"));
}
}