com.kolich.twitter.TwitterApiClient.java Source code

Java tutorial

Introduction

Here is the source code for com.kolich.twitter.TwitterApiClient.java

Source

/**
 * Copyright (c) 2012 Mark S. Kolich
 * http://mark.koli.ch
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.kolich.twitter;

import com.google.gson.reflect.TypeToken;
import com.kolich.common.functional.either.Either;
import com.kolich.http.common.response.HttpFailure;
import com.kolich.http.helpers.ByteArrayClosures.ByteArrayOrHttpFailureClosure;
import com.kolich.http.helpers.GsonClosures.GsonOrHttpFailureClosure;
import com.kolich.http.helpers.StringClosures.StringOrHttpFailureClosure;
import com.kolich.twitter.entities.Tweet;
import com.kolich.twitter.entities.TweetSearchResults;
import com.kolich.twitter.entities.User;
import com.kolich.twitter.entities.UserList;
import com.kolich.twitter.exceptions.TwitterApiException;
import com.kolich.twitter.signpost.TwitterApiCommonsHttpOAuthConsumer;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.http.HttpParameters;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.kolich.common.DefaultCharacterEncoding.UTF_8;
import static com.kolich.twitter.entities.TwitterEntity.getNewTwitterGsonInstance;
import static oauth.signpost.OAuth.decodeForm;
import static org.apache.http.HttpStatus.SC_OK;

public final class TwitterApiClient {

    private static final Logger logger__ = LoggerFactory.getLogger(TwitterApiClient.class);

    private static final String API_BEGIN_CURSOR = "-1";
    private static final String API_CURSOR_PARAM = "cursor";
    private static final String API_COUNT_PARAM = "count";
    private static final String API_MAXID_PARAM = "max_id";
    private static final String API_SINCEID_PARAM = "since_id";
    private static final String API_STATUS_PARAM = "status";
    private static final String API_QUERY_PARAM = "q";
    private static final String API_SCREEN_NAME_PARAM = "screen_name";
    private static final String API_USER_SEARCH_QUERY_PARAM = "q";
    private static final String API_USER_SEARCH_PERPAGE_PARAM = "per_page";

    /**
     * This value must be "client_auth" (referring to the xAuth process.)
     */
    private static final String API_XAUTH_MODE_CLIENT_AUTH = "client_auth";

    /**
     * The xAuth mode of authentication against the Twitter API.
     */
    private static final String API_XAUTH_MODE_PARAM = "x_auth_mode";

    /**
     * The login credential of the User the client is obtaining a
     * token on behalf of
     */
    private static final String API_XAUTH_USERNAME_PARAM = "x_auth_username";

    /**
     * The password credential of the User the client is
     * obtaining a token on behalf of
     */
    private static final String API_XAUTH_PASSWORD_PARAM = "x_auth_password";

    /**
     * The OAuth parameter that defines the URL the user will be redirected
     * back to once the OAuth authentication was successful.
     */
    private static final String API_OAUTH_CALLBACK_URL_PARAM = "oauth_callback";

    /**
     * Once we receive an OAuth token, this is the parameter name we use
     * when sending it back to the Twitter.
     */
    private static final String API_OAUTH_TOKEN_PARAM = "oauth_token";

    /**
     * Once we receive an OAuth token, this is the parameter name we use
     * when sending it back to the Twitter.
     */
    private static final String API_OAUTH_TOKEN_SECRET_PARAM = "oauth_token_secret";

    /**
     * Once we receive an OAuth token, this is the parameter name we use
     * when sending it back to the Twitter.
     */
    private static final String API_OAUTH_VERIFIER_PARAM = "oauth_verifier";

    /**
     * Once we've completed the OAuth dance with Twitter, we'll receive
     * a token, a token secret, a user_id and a username.
     */
    private static final String API_OAUTH_SCREEN_NAME_PARAM = "screen_name";

    /**
     * The default number of Tweets to retreive from the Twitter API
     * if a requested count was not specified.
     */
    private static final int API_TWEETS_DEFAULT_COUNT = 20;

    /**
     * Specifies the number of statuses to retrieve. May not be greater
     * than 200. (Note the the number of statuses returned may be smaller
     * than the requested count as retweets are stripped out of the result
     * set for backwards compatibility.) 
     */
    private static final int API_TWEETS_MAX_COUNT = 200;

    /**
     * When using Twitter's search API, you can't ask for more than
     * 100 Tweets that contain a certain hash tag.
     */
    private static final int API_SEARCH_TWEETS_MAX_COUNT = 100;

    /**
     * Specifies the number of search results to retrieve.
     * May not be greater than 20.  
     */
    private static final int API_USER_SEARCH_PERPAGE_MAX = 20;

    /**
     * If the number of desired user search results is not specified,
     * defaults to this.
     */
    private static final int API_USER_SEARCH_PERPAGE_DEFAULT = API_USER_SEARCH_PERPAGE_MAX;

    // Standard API calls, to be used once OAuth authenticated   
    private static final String FRIENDS_LIST_API_URL = "https://api.twitter.com/1.1/friends/list.json";

    private static final String FOLLOWERS_LIST_API_URL = "https://api.twitter.com/1.1/followers/list.json";

    private static final String USERS_SHOW_URL = "https://api.twitter.com/1.1/users/show.json";
    private static final String USERS_SEARCH_URL = "https://api.twitter.com/1.1/users/search.json";

    private static final String TWEET_SEARCH_URL = "https://api.twitter.com/1.1/search/tweets.json";

    private static final String STATUSES_USER_TIMELINE_URL = "https://api.twitter.com/1.1/statuses/user_timeline.json";
    private static final String STATUSES_UPDATE_URL = "https://api.twitter.com/1.1/statuses/update.json";

    // OAuth specific API resources
    private static final String OAUTH_REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token";
    private static final String OAUTH_ACCESS_TOKEN_URL = "https://api.twitter.com/oauth/access_token";
    private static final String OAUTH_AUTHENTICATE_URL = "https://api.twitter.com/oauth/authenticate";

    private final HttpClient httpClient_;

    private final String consumerKey_;
    private final String consumerKeySecret_;
    private final String apiToken_;
    private final String apiTokenSecret_;

    public TwitterApiClient(final HttpClient httpClient, final String consumerKey, final String consumerKeySecret,
            final String apiToken, final String apiTokenSecret) {
        checkNotNull(httpClient, "HttpClient cannot be null.");
        checkNotNull(consumerKey, "OAuth consumer key cannot be null.");
        checkNotNull(consumerKeySecret, "OAuth consumer key secret cannot be null.");
        checkNotNull(apiToken, "OAuth API token cannot be null.");
        checkNotNull(apiTokenSecret, "OAuth API token secret cannot be null.");
        httpClient_ = httpClient;
        consumerKey_ = consumerKey;
        consumerKeySecret_ = consumerKeySecret;
        apiToken_ = apiToken;
        apiTokenSecret_ = apiTokenSecret;
    }

    private abstract class TwitterApiGsonClosure<S> extends GsonOrHttpFailureClosure<S> {
        private final OAuthConsumer consumer_;

        public TwitterApiGsonClosure(final Type type, final OAuthConsumer consumer) {
            super(httpClient_, getNewTwitterGsonInstance(), type);
            // If consumer is null, then we need to generate a default one
            // using the key, secret, token and token secret.
            consumer_ = (consumer == null) ? oAuthBuildDefaultConsumer() : consumer;
        }

        public TwitterApiGsonClosure(final Class<S> clazz, final OAuthConsumer consumer) {
            this(TypeToken.get(clazz).getType(), consumer);
        }

        @Override
        public void before(final HttpRequestBase request) throws Exception {
            request.setURI(getFinalURI(request.getURI()));
            // OAuth sign the request.
            consumer_.sign(request);
        }

        /**
         * Override this method if you need to modify the request URI
         * before execution.  Allows the appending/inclusion of query
         * parameters (if a GET), etc.
         */
        public URI getFinalURI(final URI uri) throws Exception {
            // Default behavior is no modifications to final URI.
            return uri;
        }

        @Override
        public boolean check(final HttpResponse response, final HttpContext context) {
            return response.getStatusLine().getStatusCode() == SC_OK;
        }
    }

    private abstract class TwitterApiStringOrHttpFailureClosure extends StringOrHttpFailureClosure {
        private final OAuthConsumer consumer_;

        public TwitterApiStringOrHttpFailureClosure(final OAuthConsumer consumer) {
            super(httpClient_);
            // If consumer is null, then we need to generate a default one
            // using the key, secret, token and token secret.
            consumer_ = (consumer == null) ? oAuthBuildDefaultConsumer() : consumer;
        }

        @Override
        public void before(final HttpRequestBase request) throws Exception {
            request.setURI(getFinalURI(request.getURI()));
            // OAuth sign the request.
            consumer_.sign(request);
        }

        /**
         * Override this method if you need to modify the request URI
         * before execution.  Allows the appending/inclusion of query
         * parameters (if a GET), etc.
         */
        public URI getFinalURI(final URI uri) throws Exception {
            // Default behavior is no modifications to final URI.
            return uri;
        }

        @Override
        public boolean check(final HttpResponse response, final HttpContext context) {
            return response.getStatusLine().getStatusCode() == SC_OK;
        }
    }

    public Either<HttpFailure, User> getUser(final String username) {
        return getUser(username, null);
    }

    public Either<HttpFailure, User> getUser(final String username, final OAuthConsumer consumer) {
        checkNotNull(username, "Username cannot be null!");
        return new TwitterApiGsonClosure<User>(User.class, consumer) {
            @Override
            public URI getFinalURI(final URI uri) throws Exception {
                return new URIBuilder(uri).addParameter(API_SCREEN_NAME_PARAM, username).build();
            }
        }.get(USERS_SHOW_URL);
    }

    public Either<HttpFailure, UserList> getFriends(final String username) {
        return getFriends(username, API_BEGIN_CURSOR, null);
    }

    public Either<HttpFailure, UserList> getFriends(final String username, final String cursor,
            final OAuthConsumer consumer) {
        checkNotNull(username, "Username cannot be null!");
        checkNotNull(cursor, "Cursor cannot be null!");
        return new TwitterApiGsonClosure<UserList>(UserList.class, consumer) {
            @Override
            public URI getFinalURI(final URI uri) throws Exception {
                return new URIBuilder(uri).addParameter(API_SCREEN_NAME_PARAM, username)
                        // Cursor can be null, if so then the default value is -1
                        .addParameter(API_CURSOR_PARAM, (cursor == null) ? API_BEGIN_CURSOR : cursor).build();
            }
        }.get(FRIENDS_LIST_API_URL);
    }

    public Either<HttpFailure, UserList> getFollowers(final String username) {
        return getFollowers(username, API_BEGIN_CURSOR);
    }

    public Either<HttpFailure, UserList> getFollowers(final String username, final String cursor) {
        return getFollowers(username, cursor, null);
    }

    public Either<HttpFailure, UserList> getFollowers(final String username, final String cursor,
            final OAuthConsumer consumer) {
        checkNotNull(username, "Username cannot be null!");
        return new TwitterApiGsonClosure<UserList>(UserList.class, consumer) {
            @Override
            public URI getFinalURI(final URI uri) throws Exception {
                return new URIBuilder(uri).addParameter(API_SCREEN_NAME_PARAM, username)
                        // Cursor can be null, if so then the default value is -1
                        .addParameter(API_CURSOR_PARAM, (cursor == null) ? API_BEGIN_CURSOR : cursor).build();
            }
        }.get(FOLLOWERS_LIST_API_URL);
    }

    public Either<HttpFailure, List<Tweet>> getTweets(final String username) {
        return getTweets(username, API_TWEETS_DEFAULT_COUNT, 0L, 0L);
    }

    public Either<HttpFailure, List<Tweet>> getTweets(final String username, final int count, final long maxId,
            final long sinceId) {
        return getTweets(username,
                // Count cannot be <= zero nor can it be greater
                // than the API max we self-inforce on ourselves.
                (count <= 0 || count > API_TWEETS_MAX_COUNT) ? API_TWEETS_DEFAULT_COUNT : count, maxId, sinceId,
                // Use a default OAuthConsumer
                null);
    }

    public Either<HttpFailure, List<Tweet>> getTweets(final String username, final int count, final long maxId,
            final long sinceId, final OAuthConsumer consumer) {
        checkNotNull(username, "Username cannot be null!");
        return new TwitterApiGsonClosure<List<Tweet>>(new TypeToken<List<Tweet>>() {
        }.getType(), consumer) {
            @Override
            public URI getFinalURI(final URI uri) throws Exception {
                final URIBuilder builder = new URIBuilder(uri).addParameter(API_SCREEN_NAME_PARAM, username)
                        .addParameter(API_COUNT_PARAM, Integer.toString(count));
                if (maxId > 0L) {
                    builder.addParameter(API_MAXID_PARAM, Long.toString(maxId - 1L));
                }
                if (sinceId > 0L) {
                    builder.addParameter(API_SINCEID_PARAM, Long.toString(sinceId));
                }
                return builder.build();
            }
        }.get(STATUSES_USER_TIMELINE_URL);
    }

    public Either<HttpFailure, TweetSearchResults> searchTweets(final String query, final int count,
            final long sinceId, final OAuthConsumer consumer) {
        checkNotNull(query, "Query cannot be null!");
        return new TwitterApiGsonClosure<TweetSearchResults>(TweetSearchResults.class, consumer) {
            @Override
            public URI getFinalURI(final URI uri) throws Exception {
                final URIBuilder builder = new URIBuilder(uri).addParameter(API_QUERY_PARAM, query).addParameter(
                        API_COUNT_PARAM,
                        (count <= 0 || count > API_SEARCH_TWEETS_MAX_COUNT)
                                ? Integer.toString(API_TWEETS_DEFAULT_COUNT)
                                : Integer.toString(count));
                if (sinceId > 0L) {
                    builder.addParameter(API_SINCEID_PARAM, Long.toString(sinceId));
                }
                return builder.build();
            }
        }.get(TWEET_SEARCH_URL);
    }

    public Either<HttpFailure, TweetSearchResults> searchTweets(final String query) {
        return searchTweets(query, API_TWEETS_DEFAULT_COUNT, 0L, null);
    }

    public Either<HttpFailure, byte[]> userGetProfileImage(final String url) {
        checkNotNull(url, "Avatar URL cannot be null!");
        return new ByteArrayOrHttpFailureClosure(httpClient_).get(url);
    }

    public Either<HttpFailure, List<User>> userSearch(final String query) {
        return userSearch(query, API_USER_SEARCH_PERPAGE_DEFAULT,
                // Default OAuthConsumer
                null);
    }

    public Either<HttpFailure, List<User>> userSearch(final String query, final OAuthConsumer consumer) {
        return userSearch(query, API_USER_SEARCH_PERPAGE_DEFAULT, consumer);
    }

    public Either<HttpFailure, List<User>> userSearch(final String query, final int perPage,
            final OAuthConsumer consumer) {
        checkNotNull(query, "Query cannot be null!");
        return new TwitterApiGsonClosure<List<User>>(new TypeToken<List<User>>() {
        }.getType(), consumer) {
            @Override
            public URI getFinalURI(final URI uri) throws Exception {
                return new URIBuilder(uri).addParameter(API_USER_SEARCH_QUERY_PARAM, query)
                        .addParameter(API_USER_SEARCH_PERPAGE_PARAM,
                                (perPage <= 0 || perPage > API_USER_SEARCH_PERPAGE_MAX)
                                        ? Integer.toString(API_USER_SEARCH_PERPAGE_DEFAULT)
                                        : Integer.toString(perPage))
                        .build();
            }
        }.get(USERS_SEARCH_URL);
    }

    public Either<HttpFailure, Tweet> statusUpdate(final String text) {
        return statusUpdate(text, null);
    }

    public Either<HttpFailure, Tweet> statusUpdate(final String text, final OAuthConsumer consumer) {
        checkNotNull(text, "Tweet text cannot be null!");
        return new TwitterApiGsonClosure<Tweet>(Tweet.class, consumer) {
            @Override
            public void before(final HttpRequestBase request) throws Exception {
                final List<NameValuePair> params = new ArrayList<NameValuePair>();
                params.add(new BasicNameValuePair(API_STATUS_PARAM, text));
                // Build the request entity.
                try {
                    ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, UTF_8));
                } catch (UnsupportedEncodingException e) {
                    throw new TwitterApiException("Failed to UTF-8 " + "encode POST body.", e);
                }
                // OAuth sign the request.
                super.before(request);
            }
        }.post(STATUSES_UPDATE_URL);
    }

    public OAuthConsumer xAuthRetrieveAccessTokenConsumer(final String username, final String password) {
        checkNotNull(username, "Username cannot be null!");
        checkNotNull(password, "Password cannot be null!");
        final OAuthConsumer consumer = new CommonsHttpOAuthConsumer(consumerKey_, consumerKeySecret_);
        final HttpParameters params = new HttpParameters();
        params.put(API_XAUTH_MODE_PARAM, API_XAUTH_MODE_CLIENT_AUTH, true);
        params.put(API_XAUTH_USERNAME_PARAM, username, true);
        params.put(API_XAUTH_PASSWORD_PARAM, password, true);
        consumer.setAdditionalParameters(params);
        // Grab an OAuth access token from the API.
        final Either<HttpFailure, String> response = new TwitterApiStringOrHttpFailureClosure(consumer) {
            @Override
            public void before(final HttpRequestBase request) throws Exception {
                // xAuth authentication requires that we add the mode, username,
                // and password parameters to the POST body as well.
                final List<NameValuePair> params = new ArrayList<NameValuePair>();
                params.add(new BasicNameValuePair(API_XAUTH_MODE_PARAM, API_XAUTH_MODE_CLIENT_AUTH));
                params.add(new BasicNameValuePair(API_XAUTH_USERNAME_PARAM, username));
                params.add(new BasicNameValuePair(API_XAUTH_PASSWORD_PARAM, password));
                try {
                    ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, UTF_8));
                } catch (UnsupportedEncodingException e) {
                    throw new TwitterApiException("Failed to UTF-8 " + "encode xAuthApiAuthenticate POST body.", e);
                }
                // OAuth sign the request.
                super.before(request);
            }
        }.post(OAUTH_ACCESS_TOKEN_URL);
        // If the initial request of a proper OAuth access token failed,
        // bail and reject the operation.
        if (!response.success()) {
            throw new TwitterApiException("Failed to retreive xAuth " + "access token!",
                    response.left().getCause());
        }
        // OK, it worked.
        // Get the response body as a string, we'll need to parse it
        // out manually to retreive the params we want from the body.
        final HttpParameters decodedParams = decodeForm(response.right());
        return xAuthBuildOAuthConsumer(decodedParams.getFirst(API_OAUTH_TOKEN_PARAM),
                decodedParams.getFirst(API_OAUTH_TOKEN_SECRET_PARAM),
                decodedParams.getFirst(API_OAUTH_SCREEN_NAME_PARAM));
    }

    public OAuthConsumer xAuthBuildOAuthConsumer(final String token, final String secret, final String username) {
        final OAuthConsumer consumer = new TwitterApiCommonsHttpOAuthConsumer(consumerKey_, consumerKeySecret_,
                // Set the username if we have one.
                (username != null) ? username : null);
        consumer.setTokenWithSecret(token, secret);
        return consumer;
    }

    public OAuthConsumer xAuthRetrieveConsumer(final String token, final String secret) {
        return xAuthBuildOAuthConsumer(token, secret, null);
    }

    /**
     * Given a callback URL, retreive an OAuth request token for
     * OAuth authentication against the Twitter API.
     * @param callbackUrl
     * @return
     */
    public Either<HttpFailure, String> oAuthRetrieveRequestToken(final String callbackUrl) {
        checkNotNull(callbackUrl, "Callback URL cannot be null!");
        final OAuthConsumer consumer = new CommonsHttpOAuthConsumer(consumerKey_, consumerKeySecret_);
        // The callback URL is encoded as one of the many parameters in
        // the OAuth Http Authorization header that's sent to Twitter
        // to kick off the OAuth dance.  Note that the Http POST body
        // MUST BE EMPTY.
        final HttpParameters params = new HttpParameters();
        params.put(API_OAUTH_CALLBACK_URL_PARAM, callbackUrl, true);
        consumer.setAdditionalParameters(params);
        return new TwitterApiStringOrHttpFailureClosure(consumer) {
        }.post(OAUTH_REQUEST_TOKEN_URL);
    }

    /**
     * Requests an OAuth request token builds a valid OAuth authorize
     * URL then returns it.  The user can be re-directed to this URL
     * to authorize/deny access.
     * @param callbackUrl
     * @return
     */
    public String oAuthGetAuthorizeUrl(final String callbackUrl) {
        final Either<HttpFailure, String> response = oAuthRetrieveRequestToken(callbackUrl);
        if (!response.success()) {
            throw new TwitterApiException("Failed to build Twitter OAuth " + "authorize URL.",
                    response.left().getCause());
        }
        // Get the response body as a string, we'll need to parse it
        // out manually to retreive the params we want from the body.
        final HttpParameters params = decodeForm(response.right());
        final String token = params.getFirst(API_OAUTH_TOKEN_PARAM);
        logger__.debug("Retreived OAuth token: " + token);
        URIBuilder builder = null;
        try {
            builder = new URIBuilder(OAUTH_AUTHENTICATE_URL).addParameter(API_OAUTH_TOKEN_PARAM, token);
        } catch (URISyntaxException e) {
            // Should *never* happen, but, meh.
            throw new TwitterApiException("Failed to parse URI: " + OAUTH_AUTHENTICATE_URL, e);
        }
        return builder.toString();
    }

    public OAuthConsumer oAuthBuildConsumer(final String consumerKey, final String consumerSecret,
            final String apiToken, final String apiTokenSecret, final String username) {
        final OAuthConsumer consumer = new TwitterApiCommonsHttpOAuthConsumer(consumerKey_, consumerKeySecret_,
                username);
        consumer.setTokenWithSecret(apiToken_, apiTokenSecret_);
        return consumer;
    }

    /**
     * Given a token, a token secret, and a username returns a pre-loaded
     * {@link TwitterApiCommonsHttpOAuthConsumer} for the caller.  Has the 
     * default consumer key and consumer key secret pre-set.
     * @param apiToken
     * @param apiTokenSecret
     * @param username
     * @return
     */
    public OAuthConsumer oAuthBuildConsumer(final String apiToken, final String apiTokenSecret,
            final String username) {
        // Generate a new OAuthConsumer with the fetched token and token
        // secret pre-set for the caller.
        return oAuthBuildConsumer(consumerKey_, consumerKeySecret_, apiToken, apiTokenSecret, username);
    }

    public OAuthConsumer oAuthBuildDefaultConsumer() {
        return oAuthBuildConsumer(consumerKey_, consumerKeySecret_, apiToken_, apiTokenSecret_, null);
    }

    /**
     * Retreives an OAuth access token once a valid token and token verifier
     * have been received.  Returns a new {@link TwitterApiCommonsHttpOAuthConsumer}
     * pre-loaded with the resulting token and token secret as
     * authenticated by the user.
     * @param token
     * @param verifier
     * @return
     */
    public OAuthConsumer oAuthGetAccessTokenConsumer(final String token, final String verifier) {
        final HttpParameters params = oAuthGetAccessTokenParams(token, verifier);
        final String username = params.getFirst(API_OAUTH_SCREEN_NAME_PARAM),
                newToken = params.getFirst(API_OAUTH_TOKEN_PARAM),
                newSecret = params.getFirst(API_OAUTH_TOKEN_SECRET_PARAM);
        // Generate a new OAuthConsumer with the fetched token and token
        // secret pre-set for the caller.
        final OAuthConsumer consumer = new TwitterApiCommonsHttpOAuthConsumer(consumerKey_, consumerKeySecret_,
                username);
        consumer.setTokenWithSecret(newToken, newSecret);
        return consumer;
    }

    private final HttpParameters oAuthGetAccessTokenParams(final String token, final String verifier) {
        final Either<HttpFailure, String> response = oAuthGetAccessToken(token, verifier);
        if (!response.success()) {
            throw new TwitterApiException("Failed to retrieve OAuth " + "access token parameters.",
                    response.left().getCause());
        }
        return decodeForm(response.right());
    }

    private final Either<HttpFailure, String> oAuthGetAccessToken(final String token, final String verifier) {
        checkNotNull(token, "Token cannot be null!");
        checkNotNull(verifier, "Verifier cannot be null!");
        final OAuthConsumer consumer = new CommonsHttpOAuthConsumer(consumerKey_, consumerKeySecret_);
        final HttpParameters params = new HttpParameters();
        params.put(API_OAUTH_TOKEN_PARAM, token, true);
        params.put(API_OAUTH_VERIFIER_PARAM, verifier, true);
        consumer.setAdditionalParameters(params);
        return new TwitterApiStringOrHttpFailureClosure(consumer) {
        }.post(OAUTH_ACCESS_TOKEN_URL);
    }

}