/*
* Firetweet - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package twitter4j;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import twitter4j.auth.AccessToken;
import twitter4j.auth.Authorization;
import twitter4j.auth.AuthorizationFactory;
import twitter4j.auth.BasicAuthorization;
import twitter4j.auth.NullAuthorization;
import twitter4j.auth.OAuthAuthorization;
import twitter4j.auth.OAuthSupport;
import twitter4j.auth.RequestToken;
import twitter4j.auth.XAuthAuthorization;
import twitter4j.conf.Configuration;
import twitter4j.http.HttpClientWrapper;
import twitter4j.http.HttpParameter;
import twitter4j.http.HttpResponse;
import twitter4j.http.HttpResponseEvent;
import twitter4j.http.HttpResponseListener;
import twitter4j.internal.json.InternalJSONFactory;
import twitter4j.internal.json.InternalJSONFactoryImpl;
import static twitter4j.http.HttpResponseCode.ENHANCE_YOUR_CLAIM;
import static twitter4j.http.HttpResponseCode.SERVICE_UNAVAILABLE;
/**
* Base class of Twitter / AsyncTwitter / TwitterStream supports OAuth.
*
* @author Yusuke Yamamoto - yusuke at mac.com
*/
abstract class TwitterBaseImpl implements OAuthSupport, HttpResponseListener, TwitterConstants {
protected Configuration conf;
protected transient String screenName = null;
protected transient long id = 0;
protected transient HttpClientWrapper http;
private List<RateLimitStatusListener> rateLimitStatusListeners = new ArrayList<RateLimitStatusListener>(0);
protected InternalJSONFactory factory;
protected Authorization auth;
/* package */TwitterBaseImpl(final Configuration conf, final Authorization auth) {
this.conf = conf;
this.auth = auth;
init();
}
/**
* {@inheritDoc}
*/
public void addRateLimitStatusListener(final RateLimitStatusListener listener) {
rateLimitStatusListeners.add(listener);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof TwitterBaseImpl)) return false;
final TwitterBaseImpl that = (TwitterBaseImpl) o;
if (auth != null ? !auth.equals(that.auth) : that.auth != null) return false;
if (!conf.equals(that.conf)) return false;
if (http != null ? !http.equals(that.http) : that.http != null) return false;
if (!rateLimitStatusListeners.equals(that.rateLimitStatusListeners)) return false;
return true;
}
/**
* {@inheritDoc}
*/
public final Authorization getAuthorization() {
return auth;
}
/**
* {@inheritDoc}
*/
public Configuration getConfiguration() {
return conf;
}
/**
* {@inheritDoc}
*/
public long getId() throws TwitterException, IllegalStateException {
if (!auth.isEnabled())
throw new IllegalStateException(
"Neither user ID/password combination nor OAuth consumer key/secret combination supplied");
if (0 == id) {
fillInIDAndScreenName();
}
// retrieve the screen name if this instance is authenticated with OAuth
// or email address
return id;
}
/**
* {@inheritDoc} Basic authenticated instance of this class will try
* acquiring an AccessToken using xAuth.<br>
* In order to get access acquire AccessToken using xAuth, you must apply by
* sending an email to <a href="mailto:api@twitter.com">api@twitter.com</a>
* all other applications will receive an HTTP 401 error. Web-based
* applications will not be granted access, except on a temporary basis for
* when they are converting from basic-authentication support to full OAuth
* support.<br>
* Storage of Twitter usernames and passwords is forbidden. By using xAuth,
* you are required to store only access tokens and access token secrets. If
* the access token expires or is expunged by a user, you must ask for their
* login and password again before exchanging the credentials for an access
* token.
*
* @throws twitter4j.TwitterException When Twitter service or network is unavailable,
* when the user has not authorized, or when the client
* application is not permitted to use xAuth
* @see <a href="https://dev.twitter.com/docs/oauth/xauth">xAuth | Twitter
* Developers</a>
*/
@Override
public synchronized AccessToken getOAuthAccessToken() throws TwitterException {
Authorization auth = getAuthorization();
AccessToken oauthAccessToken;
if (auth instanceof BasicAuthorization) {
final BasicAuthorization basicAuth = (BasicAuthorization) auth;
auth = AuthorizationFactory.getInstance(conf);
if (auth instanceof OAuthAuthorization) {
this.auth = auth;
final OAuthAuthorization oauthAuth = (OAuthAuthorization) auth;
oauthAccessToken = oauthAuth.getOAuthAccessToken(basicAuth.getUserId(), basicAuth.getPassword());
} else
throw new IllegalStateException("consumer key / secret combination not supplied.");
} else {
if (auth instanceof XAuthAuthorization) {
final XAuthAuthorization xauth = (XAuthAuthorization) auth;
this.auth = xauth;
final OAuthAuthorization oauthAuth = new OAuthAuthorization(conf);
oauthAuth.setOAuthConsumer(xauth.getConsumerKey(), xauth.getConsumerSecret());
oauthAccessToken = oauthAuth.getOAuthAccessToken(xauth.getUserId(), xauth.getPassword());
} else {
oauthAccessToken = getOAuth().getOAuthAccessToken();
}
}
screenName = oauthAccessToken.getScreenName();
id = oauthAccessToken.getUserId();
return oauthAccessToken;
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException when AccessToken has already been retrieved
* or set
*/
@Override
public synchronized AccessToken getOAuthAccessToken(final RequestToken requestToken) throws TwitterException {
final OAuthSupport oauth = getOAuth();
final AccessToken oauthAccessToken = oauth.getOAuthAccessToken(requestToken);
screenName = oauthAccessToken.getScreenName();
return oauthAccessToken;
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException when AccessToken has already been retrieved
* or set
*/
@Override
public synchronized AccessToken getOAuthAccessToken(final RequestToken requestToken, final String oauthVerifier)
throws TwitterException {
return getOAuth().getOAuthAccessToken(requestToken, oauthVerifier);
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException when AccessToken has already been retrieved
* or set
*/
@Override
public synchronized AccessToken getOAuthAccessToken(final String oauthVerifier) throws TwitterException {
final AccessToken oauthAccessToken = getOAuth().getOAuthAccessToken(oauthVerifier);
screenName = oauthAccessToken.getScreenName();
return oauthAccessToken;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized AccessToken getOAuthAccessToken(final String screenName, final String password)
throws TwitterException {
return getOAuth().getOAuthAccessToken(screenName, password);
}
/* OAuth support methods */
/**
* {@inheritDoc}
*/
@Override
public RequestToken getOAuthRequestToken() throws TwitterException {
return getOAuthRequestToken(null);
}
/**
* {@inheritDoc}
*/
@Override
public RequestToken getOAuthRequestToken(final String callbackUrl) throws TwitterException {
return getOAuth().getOAuthRequestToken(callbackUrl);
}
/**
* {@inheritDoc}
*/
@Override
public RequestToken getOAuthRequestToken(final String callbackUrl, final String xAuthAccessType)
throws TwitterException {
return getOAuth().getOAuthRequestToken(callbackUrl, xAuthAccessType);
}
/**
* {@inheritDoc}
*/
public String getScreenName() throws TwitterException, IllegalStateException {
if (!auth.isEnabled())
throw new IllegalStateException(
"Neither user ID/password combination nor OAuth consumer key/secret combination supplied");
if (null == screenName) {
if (auth instanceof BasicAuthorization) {
screenName = ((BasicAuthorization) auth).getUserId();
if (-1 != screenName.indexOf("@")) {
screenName = null;
}
}
if (null == screenName) {
// retrieve the screen name if this instance is authenticated
// with OAuth or email address
fillInIDAndScreenName();
}
}
return screenName;
}
// methods declared in OAuthSupport interface
@Override
public int hashCode() {
int result = conf.hashCode();
result = 31 * result + (http != null ? http.hashCode() : 0);
result = 31 * result + rateLimitStatusListeners.hashCode();
result = 31 * result + (auth != null ? auth.hashCode() : 0);
return result;
}
@Override
public void httpResponseReceived(final HttpResponseEvent event) {
if (rateLimitStatusListeners.size() != 0) {
final HttpResponse res = event.getResponse();
final TwitterException te = event.getTwitterException();
RateLimitStatus rateLimitStatus;
int statusCode;
if (te != null) {
rateLimitStatus = te.getRateLimitStatus();
statusCode = te.getStatusCode();
} else {
rateLimitStatus = InternalJSONFactoryImpl.createRateLimitStatusFromResponseHeader(res);
statusCode = res.getStatusCode();
}
if (rateLimitStatus != null) {
final RateLimitStatusEvent statusEvent = new RateLimitStatusEvent(this, rateLimitStatus,
event.isAuthenticated());
if (statusCode == ENHANCE_YOUR_CLAIM || statusCode == SERVICE_UNAVAILABLE) {
// EXCEEDED_RATE_LIMIT_QUOTA is returned by Rest API
// SERVICE_UNAVAILABLE is returned by Search API
for (final RateLimitStatusListener listener : rateLimitStatusListeners) {
listener.onRateLimitStatus(statusEvent);
listener.onRateLimitReached(statusEvent);
}
} else {
for (final RateLimitStatusListener listener : rateLimitStatusListeners) {
listener.onRateLimitStatus(statusEvent);
}
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void setOAuthAccessToken(final AccessToken accessToken) {
getOAuth().setOAuthAccessToken(accessToken);
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void setOAuthConsumer(final String consumerKey, final String consumerSecret) {
if (null == consumerKey) throw new NullPointerException("consumer key is null");
if (null == consumerSecret) throw new NullPointerException("consumer secret is null");
if (auth instanceof NullAuthorization) {
final OAuthAuthorization oauth = new OAuthAuthorization(conf);
oauth.setOAuthConsumer(consumerKey, consumerSecret);
auth = oauth;
} else if (auth instanceof BasicAuthorization) {
final XAuthAuthorization xauth = new XAuthAuthorization((BasicAuthorization) auth);
xauth.setOAuthConsumer(consumerKey, consumerSecret);
auth = xauth;
} else if (auth instanceof OAuthAuthorization)
throw new IllegalStateException("consumer key/secret pair already set.");
}
/**
* {@inheritDoc}
*/
public void shutdown() {
if (http != null) {
http.shutdown();
}
}
@Override
public String toString() {
return "TwitterBase{" + "conf=" + conf + ", http=" + http + ", rateLimitStatusListeners="
+ rateLimitStatusListeners + ", auth=" + auth + '}';
}
protected void addParameterToList(final List<HttpParameter> params, final String paramName, final Boolean param) {
if (param != null) {
params.add(new HttpParameter(paramName, param));
}
}
protected void addParameterToList(final List<HttpParameter> params, final String paramName, final Double param) {
if (param != null) {
params.add(new HttpParameter(paramName, param));
}
}
protected void addParameterToList(final List<HttpParameter> params, final String paramName, final Integer param) {
if (param != null) {
params.add(new HttpParameter(paramName, param));
}
}
protected void addParameterToList(final List<HttpParameter> params, final String paramName, final String param) {
if (param != null) {
params.add(new HttpParameter(paramName, param));
}
}
/**
* Check the existence, and the type of the specified file.
*
* @param image image to be uploaded
* @throws twitter4j.TwitterException when the specified file is not found
* (FileNotFoundException will be nested) , or when the
* specified file object is not representing a file(IOException
* will be nested).
*/
protected void checkFileValidity(final File image) throws TwitterException {
if (!image.exists()) // noinspection ThrowableInstanceNeverThrown
throw new TwitterException(new FileNotFoundException(image + " is not found."));
if (!image.isFile()) // noinspection ThrowableInstanceNeverThrown
throw new TwitterException(new IOException(image + " is not a file."));
}
protected final void ensureAuthorizationEnabled() {
if (!auth.isEnabled())
throw new IllegalStateException(
"Authentication credentials are missing. See http://twitter4j.org/configuration.html for the detail.");
}
protected final void ensureOAuthEnabled() {
if (!(auth instanceof OAuthAuthorization))
throw new IllegalStateException(
"OAuth required. Authentication credentials are missing. See http://twitter4j.org/configuration.html for the detail.");
}
protected User fillInIDAndScreenName() throws TwitterException {
ensureAuthorizationEnabled();
final HttpParameter[] params = {new HttpParameter("include_entities", conf.isIncludeEntitiesEnabled())};
final User user = factory.createUser(http.get(conf.getRestBaseURL() + ENDPOINT_ACCOUNT_VERIFY_CREDENTIALS,
conf.getSigningRestBaseURL() + ENDPOINT_ACCOUNT_VERIFY_CREDENTIALS, params, auth));
screenName = user.getScreenName();
id = user.getId();
return user;
}
protected HttpResponse get(final String url, final String signUrl, final HttpParameter... parameters)
throws TwitterException {
// intercept HTTP call for monitoring purposes
HttpResponse response = null;
final long start = System.currentTimeMillis();
try {
response = http.get(url, signUrl, parameters, auth);
} finally {
final long elapsedTime = System.currentTimeMillis() - start;
TwitterAPIMonitor.getInstance().methodCalled(url, elapsedTime, isOk(response));
}
return response;
}
protected boolean isOk(final HttpResponse response) {
return response != null && response.getStatusCode() < 300;
}
protected HttpParameter[] mergeParameters(final Paging paging, final HttpParameter... params2) {
if (paging == null) {
return params2;
}
return mergeParameters(paging.asPostParameterArray(), params2);
}
protected HttpParameter[] mergeParameters(final HttpParameter[] params1, final HttpParameter... params2) {
if (params1 != null && params2 != null) {
final HttpParameter[] params = new HttpParameter[params1.length + params2.length];
System.arraycopy(params1, 0, params, 0, params1.length);
System.arraycopy(params2, 0, params, params1.length, params2.length);
return params;
}
if (null == params1 && null == params2) return new HttpParameter[0];
if (params1 != null)
return params1;
else
return params2;
}
protected HttpResponse post(final String url, final String sign_url, final HttpParameter... parameters)
throws TwitterException {
// intercept HTTP call for monitoring purposes
HttpResponse response = null;
final long start = System.currentTimeMillis();
try {
response = http.post(url, sign_url, parameters, auth);
} finally {
final long elapsedTime = System.currentTimeMillis() - start;
TwitterAPIMonitor.getInstance().methodCalled(url, elapsedTime, isOk(response));
}
return response;
}
protected void setFactory() {
factory = new InternalJSONFactoryImpl(conf);
}
private OAuthSupport getOAuth() {
if (!(auth instanceof OAuthSupport))
throw new IllegalStateException("OAuth consumer key/secret combination not supplied");
return (OAuthSupport) auth;
}
private void init() {
if (null == auth) {
// try to populate OAuthAuthorization if available in the
// configuration
final String consumerKey = conf.getOAuthConsumerKey();
final String consumerSecret = conf.getOAuthConsumerSecret();
// try to find oauth tokens in the configuration
if (consumerKey != null && consumerSecret != null) {
final OAuthAuthorization oauth = new OAuthAuthorization(conf);
final String accessToken = conf.getOAuthAccessToken();
final String accessTokenSecret = conf.getOAuthAccessTokenSecret();
if (accessToken != null && accessTokenSecret != null) {
oauth.setOAuthAccessToken(new AccessToken(accessToken, accessTokenSecret));
}
auth = oauth;
} else {
auth = NullAuthorization.getInstance();
}
}
http = new HttpClientWrapper(conf);
http.setHttpResponseListener(this);
setFactory();
}
@SuppressWarnings("unchecked")
private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
conf = (Configuration) stream.readObject();
auth = (Authorization) stream.readObject();
rateLimitStatusListeners = (List<RateLimitStatusListener>) stream.readObject();
http = new HttpClientWrapper(conf);
http.setHttpResponseListener(this);
setFactory();
}
private void writeObject(final ObjectOutputStream out) throws IOException {
out.writeObject(conf);
out.writeObject(auth);
final List<RateLimitStatusListener> serializableRateLimitStatusListeners = new ArrayList<RateLimitStatusListener>(
0);
for (final RateLimitStatusListener listener : rateLimitStatusListeners) {
if (listener instanceof Serializable) {
serializableRateLimitStatusListeners.add(listener);
}
}
out.writeObject(serializableRateLimitStatusListeners);
}
}