Tweeting with Tapestry

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;

    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) {
            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;
    }

}

A simple twitter based subclass TwitterService can be implemented as

//Interface
public interface TwitterService extends BaseOAuthService {

    void tweet(Token accessToken, String text);
    
    void verify(Token accessToken);

}

//Implementation
public class TwitterServiceImpl extends BaseOAuthServiceImpl implements TwitterService {

    public TwitterServiceImpl(OAuthConfiguration configuration, String apiPrefix) {
        super(configuration, TwitterApi.class, apiPrefix);
    }

    @Override
    public void tweet(Token accessToken, String text) {
        send(new Tweet(accessToken, text));
    }

    @Override
    public void verify(Token accessToken) {
        send(new TwitterVerifyCredentials(accessToken));
    }

}

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>
About these ads

Tagged: , ,

4 thoughts on “Tweeting with Tapestry

  1. Dragan Sahpaski (@dragansah) August 14, 2011 at 7:54 PM Reply

    Nice !

    Hope the commiter vote will be successful and you’ll be part of the dev team soon.

    Cheers

    • tawus August 14, 2011 at 8:51 PM Reply

      Thanks, I am very much looking forward to it. It will be really existing :)

  2. Michał Gruca (@michalgruca) August 16, 2011 at 12:57 PM Reply

    Hi Tawus,
    why are you creating static logger instead of just annotating it with @Inject ?

    Regards

    • tawus August 16, 2011 at 1:59 PM Reply

      Hi Michal,
      Just removed that. Was part of debugging.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 91 other followers

%d bloggers like this: