J2EE developer with over 7 years of experience in designing and implementing enterprise j2ee solutions based on open source technologies like Tapestry, Hibernate, Spring. Current interests include Tapestry, Plastic, Spock, Scala. Taha is a DZone MVB and is not an employee of DZone and has posted 40 posts at DZone. You can read more from them at their website. View Full User Profile

Tweeting with Tapestry

08.15.2011
| 2843 views |
  • submit to reddit

For tweeting in java, there are not many options like there are in other languages especially ruby. The best solution I have found is scribe-java. Interfacing with this library is very easy but you waste a lot of time trying to find what to do when.

The first step was to make the library talk like Tapestry. To do that we create a base service for OAuth.

//Interface
public interface BaseOAuthService {
    String getAuthorizationURL();

    Token requestAccessToken(String temporaryToken, String verifier);

    JSONObject send(OAuthResource resource);

    Token newRequestToken();

    OAuthConfiguration getConfiguration();
}

//Implementation
public class BaseOAuthServiceImpl implements BaseOAuthService {

    private OAuthService service;

    private String apiPrefix;

    private OAuthConfiguration configuration;

    private static final Logger logger = LoggerFactory.getLogger(BaseOAuthServiceImpl.class);

    public BaseOAuthServiceImpl(OAuthConfiguration configuration,
        Class<? extends Api> provider, String apiPrefix) {
        ServiceBuilder builder = new ServiceBuilder().provider(provider)
                .apiKey(configuration.getApiKey()).apiSecret(configuration.getApiSecret())
                .callback(configuration.getCallbackURL());

        if (configuration.getScope() != null) {
            builder.scope(configuration.getScope());
        }

        service = builder.build();

        this.apiPrefix = apiPrefix;
        this.configuration = configuration;
    }

    @Override
    public String getAuthorizationURL() {
        return service.getAuthorizationUrl(newRequestToken());
    }

    public Token newRequestToken() {
        return service.getRequestToken();
    }

    public Token requestAccessToken(String oAuthToken, String verifier) {

        Token accessToken = service.getAccessToken(
                new Token(oAuthToken, configuration.getApiSecret()), new Verifier(verifier));

        return accessToken;
    }

    public JSONObject send(OAuthResource resource) {
        OAuthRequest request = new OAuthRequest(resource.getMethod(), apiPrefix
                + resource.getURL());

        resource.initialize(request);
        service.signRequest(resource.getAccessToken(), request);

        Response response = request.send();
        checkResponse(response);

        resource.process(response);

        return new JSONObject(response.getBody());
    }

    private void checkResponse(Response response) {
        if (response.getCode() != HttpServletResponse.SC_OK) {
            logger.error("Failed sending request : " + response.getBody());
            throw new OAuthException("Failure sending request");
        }
    }

    public OAuthService getOAuthService() {
        return service;
    }

    public OAuthConfiguration getConfiguration(){
        return configuration;
    }
}

As you can see there is not much going on. It is just a wrapper which allows us to request a resource specified by OAuthResource. The response is checked for status and if it is not success (HTTP CODE : 200) an exception is thrown.

public interface OAuthResource {

    Verb getMethod();

    void initialize(OAuthRequest request);

    String getURL();

    Token getAccessToken();

    void process(Response response);

}

//An abstract implementation
public abstract class AbstractOAuthResource implements OAuthResource {

    private Verb method;

    private String resource;

    private Token accessToken;

    public AbstractOAuthResource(Token accessToken, Verb method, String resource){
        this.accessToken = accessToken;
        this.method = method;
        this.resource = resource;
    }

    @Override
    public Verb getMethod() {
        return method;
    }

    @Override
    public String getURL() {
        return resource;
    }

    @Override
    public Token getAccessToken(){
        return accessToken;
    }

    @Override
    public void initialize(OAuthRequest request){

    }

    @Override
    public void process(Response response) {
    }

}

getMethod returns the method which should be used to request the resource. getURL() returns the URL of the resource. getAccessToken() return the access token which is to be passed to the request for authorization purposes. initialize() method is used to pass parameters to the request and process() is used to process the response. Such an interface can be best understood by an implementation.

public class Tweet extends AbstractOAuthResource {

    private String tweet;

    public Tweet(Token token, String tweet){
        super(token, Verb.POST, "statuses/update.json");

        this.tweet = tweet;
    }

    @Override
    public void initialize(OAuthRequest request){
        request.addBodyParameter("status", tweet);
        request.addBodyParameter("wrap_links", "true");
    }

}

Twitter APIs expect a POST request for resource "statuses/update.json" for updating user status, in plain english, to tweet. The tweet text is passed as a parameter to the request in initialize() method.

The configuration to the service is passed as

public class OAuthConfiguration {

    private String apiKey;

    private String apiSecret;

    private String callbackURL;

    private String scope;

    public OAuthConfiguration(String apiKey, String apiSecret,
        String callbackURL, String scope) {
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        this.callbackURL = callbackURL;
        this.scope = scope;
    }

    public OAuthConfiguration(String apiKey, String apiSecret, String callbackURL){
        this(apiKey, apiSecret, callbackURL, null);
    }

    public String getApiKey() {
        return apiKey;
    }

    public String getApiSecret() {
        return apiSecret;
    }

    public String getCallbackURL() {
        return callbackURL;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope){
        this.scope = scope;
    }

}

Usage

To use this service, let’s create a component which gives us access to a twitter account

@Events({OAuthConstants.CONNECTION_ESTABLISHED, OAuthConstants.CONNECTION_FAILED})
public class TwitterConnect implements ClientElement {
    @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL)
    private String clientId;

    private String assignedClientId;

    @Inject
    private JavaScriptSupport javaScriptSupport;

    @Inject
    private ComponentResources resources;

    @Inject
    private TwitterService twitterService;

    void setupRender() {
        assignedClientId = javaScriptSupport.allocateClientId(clientId);
    }

    @Override
    public String getClientId() {
        return assignedClientId;
    }

    URL onConnectToTwitter() throws MalformedURLException {
        return new URL(twitterService.getAuthorizationURL());
    }

    Object onAuthorize(
        @RequestParameter(value = "oauth_verifier", allowBlank = true) final String verifier,
        @RequestParameter(value = "oauth_token", allowBlank = true) String oAuthToken,
        @RequestParameter(value = "denied", allowBlank = true) String denied) {

        if(verifier != null){
            return accessGranted(oAuthToken, verifier);
        }else {
            return accessDenied(denied);
        }
    }

    private Object accessGranted(String oAuthToken, String verifier) {
        Token accessToken = twitterService.requestAccessToken(oAuthToken, verifier);

        CaptureResultCallback<Object> callback = new CaptureResultCallback<Object>();

        boolean handled = resources.triggerEvent(OAuthConstants.CONNECTION_ESTABLISHED,
          new Object[] {
                accessToken}, callback);

        if (handled) {
            return callback.getResult();
        }

        return null;
    }

    private Object accessDenied(String denied) {
        CaptureResultCallback<Object> callback = new CaptureResultCallback<Object>();

        boolean handled = resources.triggerEvent(OAuthConstants.CONNECTION_FAILED,
            new Object[] {
                denied}, callback);

        if (handled) {
            return callback.getResult();
        }

        return null;
    }

}

public class OAuthConstants {

    public static final String DEFAULT_TWITTER_API_PREFIX = "http://api.twitter.com/1/";

    public static final String CONNECTION_ESTABLISHED = "connectionEstablished";

    public static final String CONNECTION_FAILED = "connectionFailed";

}
<t:container xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>

   <a href='#' t:type='eventlink' t:event='connectToTwitter'><t:body/></a>

</t:container>

When the link is clicked, the browser is redirected to twitter’s authorization link. Before returning the URL, getAuthorizationURL() first requests a request token from twitter. The twitter authorizes and redirects the browser to the callback URL(callback URL is configured in the application module) which triggers onAuthorize(), which in term based on whether the authorization was successful or not triggers different events.

The services have to be contributed to the application module

public TwitterService buildTwitterService() {

    OAuthConfiguration configurer = new OAuthConfiguration("MY_API_KEY",
            "MY_API_SECRET",
            "http://127.0.0.1:9090/tweet.twitterconnect:authorize");

    return new TwitterServiceImpl(configurer, OAuthConstants.DEFAULT_TWITTER_API_PREFIX);
}

Please note the URL has to point to the event handler of TwitterConnect's authorize event. (I know this part is a hack, but will replace with a better solutions soon)

And finally the page using it. As you would have guessed from the URL above, twitter page is Tweet

public class Tweet {

    @Inject
    private TwitterService twitterService;

    @SuppressWarnings("unused")
    @Property
    @Persist(PersistenceConstants.FLASH)
    private String message;

    void onConnectionEstablishedFromTwitterConnect(Token accessToken) {

        twitterService.tweet(accessToken,  "Hello from scribe-twitter at " + new Date());

        message = "Tweeted";
    }

    void onConnectionFailed(String denied){
        message = "Failed";
    }

}
<html xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>

   <body>
         ${message}<br/>
      <a href='#' t:id='twitterconnect' t:type='twitterConnect'>Connect to Twitter</a>
   </body>

</html>

 

From http://tawus.wordpress.com/2011/08/14/tweeting-with-tapestry/

Published at DZone with permission of Taha Siddiqi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)