/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
**/
package org.codice.ddf.catalog.twitter.source;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.catalog.data.ContentType;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.data.impl.MetacardImpl;
import ddf.catalog.data.impl.ResultImpl;
import ddf.catalog.operation.QueryRequest;
import ddf.catalog.operation.ResourceResponse;
import ddf.catalog.operation.SourceResponse;
import ddf.catalog.operation.impl.SourceResponseImpl;
import ddf.catalog.resource.ResourceNotFoundException;
import ddf.catalog.resource.ResourceNotSupportedException;
import ddf.catalog.resource.ResourceReader;
import ddf.catalog.source.FederatedSource;
import ddf.catalog.source.SourceMonitor;
import ddf.catalog.source.UnsupportedQueryException;
import twitter4j.GeoLocation;
import twitter4j.Query;
import twitter4j.QueryResult;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.conf.Configuration;
import twitter4j.conf.ConfigurationBuilder;
public class TwitterSource implements FederatedSource {
private static final Logger LOGGER = LoggerFactory.getLogger(TwitterSource.class);
TwitterFactory twitterFactory;
String id;
ResourceReader resourceReader;
String consumerKey;
String consumerSecret;
public TwitterSource() {
}
public void init() {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
Configuration configuration = configurationBuilder.setOAuthConsumerKey(consumerKey)
.setOAuthConsumerSecret(consumerSecret)
.setApplicationOnlyAuthEnabled(true)
.build();
twitterFactory = new TwitterFactory(configuration);
}
public void destroy() {
twitterFactory = null;
}
@Override
public ResourceResponse retrieveResource(URI uri, Map<String, Serializable> arguments)
throws IOException, ResourceNotFoundException, ResourceNotSupportedException {
return resourceReader.retrieveResource(uri, arguments);
}
@Override
public Set<String> getSupportedSchemes() {
return Collections.emptySet();
}
@Override
public Set<String> getOptions(Metacard metacard) {
return Collections.emptySet();
}
@Override
public boolean isAvailable() {
Twitter instance = twitterFactory.getInstance();
try {
instance.getOAuth2Token();
return true;
} catch (TwitterException e) {
LOGGER.error("Unable to get OAuth2 token.", e);
return false;
}
}
@Override
public boolean isAvailable(SourceMonitor callback) {
if (isAvailable()) {
callback.setAvailable();
return true;
} else {
callback.setUnavailable();
return false;
}
}
@Override
public SourceResponse query(QueryRequest request) throws UnsupportedQueryException {
Twitter instance = twitterFactory.getInstance();
try {
instance.getOAuth2Token();
} catch (TwitterException e) {
throw new UnsupportedQueryException("Unable to get OAuth2 token.", e);
}
TwitterFilterVisitor visitor = new TwitterFilterVisitor();
request.getQuery()
.accept(visitor, null);
Query query = new Query();
query.setCount(request.getQuery()
.getPageSize());
if (visitor.hasSpatial()) {
GeoLocation geoLocation = new GeoLocation(visitor.getLatitude(),
visitor.getLongitude());
query.setGeoCode(geoLocation, visitor.getRadius(), Query.Unit.km);
}
if (visitor.getContextualSearch() != null) {
query.setQuery(visitor.getContextualSearch()
.getSearchPhrase());
}
if (visitor.getTemporalSearch() != null) {
Calendar.Builder builder = new Calendar.Builder();
builder.setInstant(visitor.getTemporalSearch()
.getStartDate());
Calendar calendar = builder.build();
query.setSince(calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH) + "-"
+ calendar.get(Calendar.DAY_OF_MONTH));
builder = new Calendar.Builder();
builder.setInstant(visitor.getTemporalSearch()
.getEndDate());
calendar = builder.build();
query.setUntil(calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH) + "-"
+ calendar.get(Calendar.DAY_OF_MONTH));
}
QueryResult queryResult;
try {
queryResult = instance.search()
.search(query);
} catch (TwitterException e) {
throw new UnsupportedQueryException(e);
}
List<Result> resultList = new ArrayList<>(queryResult.getCount());
resultList.addAll(queryResult.getTweets()
.stream()
.map(status -> new ResultImpl(getMetacard(status)))
.collect(Collectors.toList()));
return new SourceResponseImpl(request, resultList);
}
private Metacard getMetacard(Status status) {
MetacardImpl metacard = new MetacardImpl();
metacard.setSourceId(id);
metacard.setId(String.valueOf(status.getId()));
metacard.setTitle(status.getText());
metacard.setMetadata("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<Resource>" +
"<name>" + status.getText() + "</name>" +
"</Resource>");
metacard.setCreatedDate(status.getCreatedAt());
metacard.setModifiedDate(status.getCreatedAt());
metacard.setEffectiveDate(status.getCreatedAt());
metacard.setPointOfContact(status.getUser()
.getName());
if (status.getURLEntities() != null && status.getURLEntities().length > 0) {
try {
metacard.setResourceURI(new URI(status.getURLEntities()[0].getExpandedURL()));
} catch (URISyntaxException e) {
LOGGER.error("Unable to set resource URI.", e);
}
} else if (status.getMediaEntities() != null && status.getMediaEntities().length > 0) {
try {
metacard.setResourceURI(new URI(status.getMediaEntities()[0].getExpandedURL()));
} catch (URISyntaxException e) {
LOGGER.error("Unable to set resource URI.", e);
}
} else if (status.getExtendedMediaEntities() != null
&& status.getExtendedMediaEntities().length > 0) {
try {
metacard.setResourceURI(
new URI(status.getExtendedMediaEntities()[0].getExpandedURL()));
} catch (URISyntaxException e) {
LOGGER.error("Unable to set resource URI.", e);
}
}
GeoLocation geoLocation = status.getGeoLocation();
if (geoLocation != null) {
metacard.setLocation(
"POINT (" + geoLocation.getLongitude() + " " + geoLocation.getLatitude() + ")");
}
return metacard;
}
@Override
public Set<ContentType> getContentTypes() {
return Collections.emptySet();
}
@Override
public String getVersion() {
return "1";
}
@Override
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String getTitle() {
return "Twitter Federated Source";
}
@Override
public String getDescription() {
return "Query using the Twitter API.";
}
@Override
public String getOrganization() {
return "Codice";
}
public String getConsumerKey() {
return consumerKey;
}
public void setConsumerKey(String consumerKey) {
this.consumerKey = consumerKey;
}
public String getConsumerSecret() {
return consumerSecret;
}
public void setConsumerSecret(String consumerSecret) {
this.consumerSecret = consumerSecret;
}
public void setResourceReader(ResourceReader resourceReader) {
this.resourceReader = resourceReader;
}
}