/*
* Copyright 2005-2014 Red Hat, Inc.
* Red Hat 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.hornetq.integration.twitter.impl;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.hornetq.api.core.SimpleString;
import org.hornetq.core.persistence.StorageManager;
import org.hornetq.core.postoffice.Binding;
import org.hornetq.core.postoffice.PostOffice;
import org.hornetq.core.server.ConnectorService;
import org.hornetq.core.server.ServerMessage;
import org.hornetq.core.server.impl.ServerMessageImpl;
import org.hornetq.integration.twitter.TwitterConstants;
import org.hornetq.twitter.HornetQTwitterLogger;
import org.hornetq.utils.ConfigurationHelper;
import twitter4j.GeoLocation;
import twitter4j.Paging;
import twitter4j.Place;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.http.AccessToken;
/**
* IncomingTweetsHandler consumes from twitter and forwards to the
* configured HornetQ address.
*/
public class IncomingTweetsHandler implements ConnectorService
{
private final String connectorName;
private final String consumerKey;
private final String consumerSecret;
private final String accessToken;
private final String accessTokenSecret;
private final String queueName;
private final int intervalSeconds;
private final StorageManager storageManager;
private final PostOffice postOffice;
private Paging paging;
private Twitter twitter;
private boolean isStarted = false;
private final ScheduledExecutorService scheduledPool;
private ScheduledFuture<?> scheduledFuture;
public IncomingTweetsHandler(final String connectorName,
final Map<String, Object> configuration,
final StorageManager storageManager,
final PostOffice postOffice,
final ScheduledExecutorService scheduledThreadPool)
{
this.connectorName = connectorName;
this.consumerKey = ConfigurationHelper.getStringProperty(TwitterConstants.CONSUMER_KEY, null, configuration);
this.consumerSecret = ConfigurationHelper.getStringProperty(TwitterConstants.CONSUMER_SECRET, null, configuration);
this.accessToken = ConfigurationHelper.getStringProperty(TwitterConstants.ACCESS_TOKEN, null, configuration);
this.accessTokenSecret = ConfigurationHelper.getStringProperty(TwitterConstants.ACCESS_TOKEN_SECRET, null, configuration);
this.queueName = ConfigurationHelper.getStringProperty(TwitterConstants.QUEUE_NAME, null, configuration);
Integer intervalSeconds = ConfigurationHelper.getIntProperty(TwitterConstants.INCOMING_INTERVAL, 0, configuration);
if (intervalSeconds > 0)
{
this.intervalSeconds = intervalSeconds;
}
else
{
this.intervalSeconds = TwitterConstants.DEFAULT_POLLING_INTERVAL_SECS;
}
this.storageManager = storageManager;
this.postOffice = postOffice;
this.scheduledPool = scheduledThreadPool;
}
public void start() throws Exception
{
Binding b = postOffice.getBinding(new SimpleString(queueName));
if (b == null)
{
throw new Exception(connectorName + ": queue " + queueName + " not found");
}
paging = new Paging();
TwitterFactory tf = new TwitterFactory();
this.twitter = tf.getOAuthAuthorizedInstance(this.consumerKey,
this.consumerSecret,
new AccessToken(this.accessToken,
this.accessTokenSecret));
this.twitter.verifyCredentials();
// getting latest ID
this.paging.setCount(TwitterConstants.FIRST_ATTEMPT_PAGE_SIZE);
// If I used annotations here, it won't compile under JDK 1.7
ResponseList res = this.twitter.getHomeTimeline(paging);
this.paging.setSinceId(((Status) res.get(0)).getId());
HornetQTwitterLogger.LOGGER.debug(connectorName + " initialise(): got latest ID: " + this.paging.getSinceId());
// TODO make page size configurable
this.paging.setCount(TwitterConstants.DEFAULT_PAGE_SIZE);
scheduledFuture = this.scheduledPool.scheduleWithFixedDelay(new TweetsRunnable(),
intervalSeconds,
intervalSeconds,
TimeUnit.SECONDS);
isStarted = true;
}
public void stop() throws Exception
{
if (!isStarted)
{
return;
}
scheduledFuture.cancel(true);
paging = null;
isStarted = false;
}
public boolean isStarted()
{
return isStarted;
}
private void poll() throws Exception
{
// get new tweets
// If I used annotations here, it won't compile under JDK 1.7
ResponseList res = this.twitter.getHomeTimeline(paging);
if (res == null || res.size() == 0)
{
return;
}
for (int i = res.size() - 1; i >= 0; i--)
{
Status status = (Status) res.get(i);
ServerMessage msg = new ServerMessageImpl(this.storageManager.generateUniqueID(),
TwitterConstants.INITIAL_MESSAGE_BUFFER_SIZE);
msg.setAddress(new SimpleString(this.queueName));
msg.setDurable(true);
msg.encodeMessageIDToBuffer();
putTweetIntoMessage(status, msg);
this.postOffice.route(msg, false);
HornetQTwitterLogger.LOGGER.debug(connectorName + ": routed: " + status.toString());
}
this.paging.setSinceId(((Status) res.get(0)).getId());
HornetQTwitterLogger.LOGGER.debug(connectorName + ": update latest ID: " + this.paging.getSinceId());
}
private void putTweetIntoMessage(final Status status, final ServerMessage msg)
{
msg.getBodyBuffer().writeString(status.getText());
msg.putLongProperty(TwitterConstants.KEY_ID, status.getId());
msg.putStringProperty(TwitterConstants.KEY_SOURCE, status.getSource());
msg.putLongProperty(TwitterConstants.KEY_CREATED_AT, status.getCreatedAt().getTime());
msg.putBooleanProperty(TwitterConstants.KEY_IS_TRUNCATED, status.isTruncated());
msg.putLongProperty(TwitterConstants.KEY_IN_REPLY_TO_STATUS_ID, status.getInReplyToStatusId());
msg.putIntProperty(TwitterConstants.KEY_IN_REPLY_TO_USER_ID, status.getInReplyToUserId());
msg.putBooleanProperty(TwitterConstants.KEY_IS_FAVORITED, status.isFavorited());
msg.putBooleanProperty(TwitterConstants.KEY_IS_RETWEET, status.isRetweet());
msg.putObjectProperty(TwitterConstants.KEY_CONTRIBUTORS, status.getContributors());
GeoLocation gl;
if ((gl = status.getGeoLocation()) != null)
{
msg.putDoubleProperty(TwitterConstants.KEY_GEO_LOCATION_LATITUDE, gl.getLatitude());
msg.putDoubleProperty(TwitterConstants.KEY_GEO_LOCATION_LONGITUDE, gl.getLongitude());
}
Place place;
if ((place = status.getPlace()) != null)
{
msg.putStringProperty(TwitterConstants.KEY_PLACE_ID, place.getId());
}
}
public String getName()
{
return connectorName;
}
private final class TweetsRunnable implements Runnable
{
/**
* TODO streaming API support
* TODO rate limit support
*/
public void run()
{
// Avoid canceling the task with RuntimeException
try
{
poll();
}
catch (Throwable t)
{
HornetQTwitterLogger.LOGGER.errorPollingTwitter(t);
}
}
}
}