Tuesday Feb 07, 2012

JAX-RS 2.0 Early Draft Explained - Java EE 7 Making Progress


JAX-RS 2.0 Early Draft has been available for about 3 months now. JAX-RS 2.0, just like JPA 2.1, was one one of the first JSRs to be filed as part of Java EE 7. Several other specifications in Java EE 7 have released early drafts as well (JavaServer Faces 2.2CDI 1.1, EJB 3.2, and more coming as well) and I'll cover them in later blogs.

Here are the topics covered so far:
JAX-RS 2.0 is a brand new specification and here are the main highlights so far:
  • Client API: The HTTPUrlConnection is too low level and is not RESTful-oriented. Invoking a RESTful resource using this class would look like:

    URL url = new URL("http://.../atm/balance");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setDoInput(true);
    conn.setDoOutput(false);
               
    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = br.readLine()) != null) {
        out.println(line);
    }


    Notice, how much code has to be written and its brittle. Some JAX-RS 1.0/1.1 implementations already provide a higher-level client-side API to access the Web resources. For example, read TOTD #57 for more details about Jersey Client API. A slightly more advanced sample to access Twitter timeline using Jersey Client API is described in TOTD #143.

    JAX-RS 2.0 introduces a standard Client API to in javax.ws.rs.client package to access the Web resource. It also share features with JAX-RS server API (readers/writers).

    A simple usage looks like:

    Client client = ClientFactory.newClient();
    String balance = client.target("http://.../atm/balance")
                           .request("text/plain")
                           .get(String.class);


    Instead of Client client = ClientFactory.newClient(), would you like @Inject Client client then vote for JAX_RS_SPEC-170.

    Path and query parameters can be easily specified using the builder pattern as shown below:

    Client client = ClientFactory.newClient();
    String balance = client.target("http://.../atm/{card}/balance")
                           .pathParam("card", "1111222233334444")
                           .queryParam("pin", "1234")
                           .request("text/plain")
                           .get(String.class);


    See how the template in the target path is automatically substituted with the correct value. The request type is specified to be "text/plain" and GET method of this web resource is invoked. This will be translated to:

    http://.../atm/1111222233334444/balance?pin=1234

    A POST request will look like:

    Money balance = client.target("http://.../atm/{card}/withdraw")
                          .pathParam("card", "1111222233334444")
                          .queryParam("pin", "1234")
                          .request("application/json")
                          .post(text("50.0"), Money.class);

    There is also generic command pattern that enables separation of concern between the creator and submitteruseful for batch processing using Invocation. And the code would look like:

    Invocation inv1 = client.target("http://.../atm/{card}/balance")
                            .pathParam("card", "1111222233334444")
                            .queryParam("pin", "1234")
                            .request("text/plain")
                            .buildGet();


    Invocation inv2 = client.target("http://.../atm/{card}/withdraw")
                            .pathParam("card", "1111222233334444")
                            .queryParam("pin", "1234")
                            .request("application/json")
                            .buildPost(text("50.0"));

    And once the Invocations are ready then they can be invoked.

  • Filters and Handlers: The filters and handlers allow app developers to perform message request pre-processing and response post-processing via well-defined extension points on the client- and server-side. This is yet another feature that was supported by several JAX-RS 1.0/1.1 implementations with each using slightly different semantics and now getting standardized in JAX-RS 2.0.

    Filters are non-wrapping extension points, allow pre-processing without modifying the request itself. A filter implements interface RequestFilter or ResponseFilter or both and is annotated with @Provider. A logging filter that simply logs the message may look like:

    @Provider
    class LoggingFilter implements RequestFilter, ResponseFilter {

        @Override
        public FilterAction preFilter(FilterContext ctx) throws IOException {
            logRequest(ctx.getRequest());
            return FilterAction.NEXT;
        }

        @Override
        public FilterAction postFilter(FilterContext ctx) throws IOException {
            logResponse(ctx.getResponse());
            return FilterAction.NEXT;
        }
    }

    Multiple filters are grouped in filter chains. The response from preFilter and postFilter indicate whether the next filter in the chain need to be executed (FilterAction.NEXT) or stopped (FilterAction.STOP).

    Handlers provide wrapping extension points. A handler implements interface ReadFromHandler or WriteToHandler or both and is annotated with @Provider. A GZIP filter that provides deflate and inflate capabilities may look like:

    @Provider
    class GzipHandler implements ReadFromHandler, WriteToHandler {
        @Override
        public Object readFrom(ReadFromHandlerContext ctx) throws IOException {
            InputStream old = ctx.getInputStream();
            ctx.setInputStream(new GZIPInputStream(old));
            try {
                return ctx.proceed();
            } finally {
                ctx.setInputStream(old);
            }
        }

        @Override
        public Object writeTo(WriteToHandlerContext ctx) throws IOException {
            OutputStream old = ctx.getOutputStream();
            GZIPOutputStream gzipOutputStream =
    new GZIPOutputStream(old);
            ctx.setInputStream(gzipOutputStream);
            try {
                return ctx.proceed();
            } finally {
                gzipOutputStream.finish();
                ctx.setOutputStream(old);
            }
        }
    }

    Multiple handlers are grouped in handler chains. The proceed method must be explicitly called in order for the next handler in the chain to be invoked.

    In the direction of flow the filters always executed before handlers. The following diagram shows the exact execution order on client and server-side:



    The handlers and filters can be associated to each method of a resource specifically using @NameBinding. The specification defines @GlobalBinding to associate handlers and filters to all methods of a resource but the recent version of the specification removes it and makes it a default. Also look at JAX_RS_SPEC-146 that asks for a mechanism to override the global filters/handlers.

    The dynamic binding, enabled by implementing DynamicBinding, provide more control on the association with resources and methods.
  • Hypermedia: Linking resources together is one of the main RESTful principles. There are structural links that are used to avoid sending a complete representation of a resource and enable lazy loading. The clients can follow these type of links to retrieve the "pieces" they need. A transitional link is used to update the state of a resource and is typically identified by a "rel" attribute. Structural links are normally in the entity; transitional links could be in link headers or the entity.

    JAX-RS 2.0 will only support transitional links in headers using newly added Link and LinkBuilder classes. The proposed Client API can also create a target from a link. The code may look like:

    Response r = client.target("/product").request("application/json").get();
    ResponseHeaders rh = r.getHeaders();
    if (rh.hasLink("ship")) {
        client.invocation(rh.getLink("ship")).invoke();
    }


    On the server side, ResponseBuilder has support for adding one or more link headers. The code may look like:

    @Path("/products")
    public class MyResponse {

        @GET
        @Path("{id}")
        @Produces({"application/json", "application/xml"})
        public Response getProduct(@PathParam("id")int id) {
            Product product = new Product(id);
            return Response
                    .ok(product)
                    .link("http://.../orders/" + id + "/ship", "ship")
                    .build();
        }
    }


  • Validation: Web resources must validate data received in query or header parameters or entity bodies. Currently this validation has to be performed in the application code. The Bean Validation specification already defines a extensible mechanism to specify validation constraints on a bean. So the JAX-RS specification leverages that and introduces support for declarative validation. The constraint annotations can be specified in public constructor parameters, method parameters, fields and bean properties. In addition, they can also decorate resource classes, entity parameters and resource methods. Here is a sample class augmented with constraint annotations:

    @Path("/")
    class ProductResource {

        @POST
        @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
        public void addProduct(@NotNull @FormParam("productName") String name,
                               @NotNull @Category @FormParam("category") String category) {
            . . .
        }
    }


    The @NotNull is a pre-defined constraint in the Bean Validation API and ensures that the target parameters are not null. The @Category is a user-defined constraint using the extensible mechanism provided by the Bean Validation API. If the constraints are violated then the resource method is not invoked and instead a response with status code 400 (Bad Request) and an entity that describe the violations is returned to the client.

    Here is a sample code of how to validate request entity bodies when they are mapped to resource method parameters:

    @CheckProduct
    class Product { . . . }

    @Path("/")
    class ProductResource {

        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        public void addProduct(@Valid Product product) {
            . . .
        }
    }


    The presence of @Valid (a pre-defined annotation in Bean Validation API) next to the method parameter ensures that the @CheckProduct constraint is called to verify the mapped entity.

    The JAX-RS specification also defines the sequence to validate root resource class instances. The recommendation is to return as many violations as possible instead of aborting after the first violation is encountered.
  • Asynchronous Processing: JAX-RS 2.0 introduces asynchronous processing on server- and client-side APIs for the usual reasons. The server-side code will look like:

    @Path("/async")
    class ProductResource {
        @Context ExecutionContext ctx;

        @GET
        @Suspend
        public Product longOp() {
            Executors.newSingleThreadExecutor().submit(
                new Runnable() {
                    public void run() {
                        Proruct product = longQueryFromDatabase();
                        ctx.resume(product);
                    }
                }
            );
        }
    }

    The longOp method is invoked when this resource is accessed using GET, forks a new thread, and returns immediately without producing the result. Once longQueryFromDatabase returns the product then the connection is resumed and the response is returned by calling ctx.resume and setting the value.

    ExecutionContext also provide suspend() that allows to override the values, such as timeout, specified in the annotation based upon the runtime state.

    On the client-side, the code will look like:

    Client client = ClientFactory.newClient();
    Future<String> future = client.target("http://.../atm/{card}/balance")
                                  .pathParam("card", "1111222233334444")
                                  .queryParam("pin", "1234")
           
                           .request("text/plain")
                  
                    .async()
                         
             .get();

    The async() is called during building the client request. The return Future<String> can be used to query/cancel the status of execution on the server-side using isDone and cancel. Once the response is ready then Future<T>.get() is invoked to receive an instance of T if the response was successful or null if the invocation failed.

    Optionally an InvocationCallback<T> may be registered during the request invocation. The completed method is called when the invocation completes successfully and a response is available and failed method is called when the invocation fails. The code looks like:

    Future<String> future = client.target("http://.../atm/{card}/balance")
                                  .pathParam("card", "1111222233334444")
                                  .queryParam("pin", "1234")
           
                           .request("text/plain")
                  
                    .async()
                         
             .get(
                                      new InvocationCallback<String>() {
                                          @Override
                                          public void completed(String result) {
                                              // got the correct result
                                              System.out.println(result);
                                          }

                                          @Override
                                          public void failed(InvocationException error) {
                                              // ouch, got an error!
                                              System.err.println(error.getCause());
                                          }
                                      }
                                   );


    Notice, the type of the result is specified as type parameter to InvocationCallback.

    Client and server developers optimize their resources (threads) depending upon their needs and are independent of each other. A client really can't tell if a resource is implemented asynchronously or not and shouldn't even need to know.

  • Improved connection negotiation: This would allow a server to specify a preferred MIME type if the client does not care about it. This can be easily specified using the "qs" qualifier as shown below:

    @Path("/")
    class ProductResource {

        @GET
        @Produces({ "text/xml
    ;qs=0.75", "application/json"})
        public Product[] getProducts() {
            . . .
        }
    }


    The default server-side qs-value and the final order of server-side served types preferences follow the same rules as those specified for HTTP Accept Header. Per those rules an unspecified value takes the default value of 1. So the above @Produces rule says "application/json" will be served as the preferred type if there is no Accept header from the client. The section 3.5 talks more about qs parameter and the section 3.8 in the specification provide complete details about how media type of the response is chosen by a JAX-RS runtime.

The Appendix D in the specification provide a comprehensive list of changes from the previous version of the specification.

Here are some final set of references for you:

And of course, it'll all be delivered as part of GlassFish 4.0!

Learn the latest and greatest about JAX-RS 2.0 from Marek Potociar's talk on JAX-RS at Devoxx 2011:

Tuesday Jan 11, 2011

JAX-RS 2.0 and JPA 2.1 JSRs filed ... Java EE 7 moves forward!

JSR 338 (JPA 2.1) and JSR 339 (JAX-RS 2.0) are the first formal steps in moving Java EE 7 platform forward!

The key features considered in scope of JPA 2.1 are:

  • Support for the use of custom types and transformation methods in object/relational mapping.
  • Support for the use of "fetch groups" and/or "fetch plans" to provide further control over data that is fetched, detached, copied, and/or used in merging.
  • Support for the specification of immutable attributes and readonly entities.
  • Support for user-configurable naming strategies for use in O/R mapping and metamodel generation.
  • More flexibility in the use of generated values; support for UUID generator type.
  • Additional mapping metadata to provide better standardization for schema generation.
  • Support for multitenancy.
  • Additional event listeners and callback methods; availability of entity manager to callbacks.
  • Methods for dirty detection.
  • Improved ability to control persistence context synchronization.
  • Additional unwrap methods to support use of vendor extensions.
  • Support for dynamic definition of persistence unit, including object/relational mapping information.
  • Extension of metamodel API to object/relational mapping information.
  • Improvements to the Java Persistence query language and criteria APIs

More details in JSR 338. Click here if you are interested in joining this Expert Group.

The key features considered in scope of JAX-RS 2.0 are:

  • Client API - a low-level using a builder pattern and a higher level leveraging the former one
  • Hypermedia processing on client and server
  • MVC architecture compatible with JAX-RS programming model
  • Integration with Bean Validation for parameter validation
  • Tighter integration with JSR 330 annotations, such as @Inject
  • Asynchronous request processing
  • Sophisiticated server-side content negotiation
  • More ease-of-development following DRY principles

More details in JSR 339. Click here if you are interested in joining this Expert Group. This Expert Group will have a public observer alias to monitor discussions.

The JSR ballot closes on Jan 24, 2011. After the JSRs are approved the Expert Groups start discussion on each and every item of the proposal. And you'll start seeing the promoted builds and integrations into GlassFish after that.

Stay tuned to hear details as Java EE 7 continues its march forward. In the meanwhile, you can download GlassFish 3.1 promoted build (soon to be final) that provides full Java EE 6 functionality along with centralized administration and high availability.

Technorati: javaee7 glassfish jaxrs jpa restful persistence

Friday Oct 15, 2010

Java EE 6 & GlassFish @ Silicon Valley Code Camp 2010 Trip Report - Slides Now Available

What's common between 350 Mountain Mike's Pizza, 920 Erik's Deli Sandwiches, 29 huge jugs of Peet's Coffee, and 194 geek sessions ?

It takes 1876 developers, hackers, architects, technology honchos etc to consume all of them over a weekend :-)

Yes, we are talking about Silicon Valley Code Camp 5.0!

This is a for the developer community, by the developer community, and to the developer community event focused on Agile, Cloud, HTML5, Google Developer Tools & Platforms, Java, Web services, and many other topics. The code camp website shows attendance records from the previous years as:

A slightly updated view of this data is:

As you can see, there is steady year/year growth in the number of sessions, registration, and attendance. This year specifically has seen about 60% growth on registrations and 80% on the actual attendance. The ratio between registered/attended was also steady for the first few years and has also gone higher this year. This event has truly grown organically over the past years and is the second biggest conference, with Java focus, in the USA after JavaOne! Oracle certainly was a platinum sponsor of this conference. Are there any other bigger conferences that I don't know of ? ;-)

It would be interesting to revisit this pattern if the event starts charging a nominal (may be optional) fees from all the attendees. However the main website highlights three points:

  • by/for the developer community
  • always free
  • never occur during work hours

GlassFish team presented several sessions at the Code Camp:

  1. Java EE 6: Doing More with Less
  2. Java EE 6 Tooling
  3. Introduction to JAX-RS
  4. Servlets 3.0: Asynchronous, Extensible, Easy-to-use
  5. OSGi and Java EE in GlassFish
  6. Running your Java EE 6 applications in the Cloud

I could not deliver #5 due to a conflict but slides from all the sessions are now available:

Here are some key pointers for you:

Check out the complete list of sessions by Oracle at the Code Camp. Please enter session evaluations at http://siliconvalley-codecamp.com/SessionsOverview.aspx (make sure you are logged in and then click the link at the end of the session you attended that says 'Evaluate').

The Android App was pretty useful, did not try the iPhone App. I wonder if there was a session on "How I created the Android/iPhone App" :-)

Personally, this was my fourth code camp (2009, 2008, 2007) and I enjoyed meeting the local geek community. I could barely spend Saturday morning at the code camp and delivered two of my sessions but its always fun to meet the usual suspects. Many thanks to Peter Kellner, Van Riper, Tammy Baker, Kevin Nilson, other organizers, and many other volunteers for running a great show!

Couple of suggestions for next year ...

  • Expose the RESTful API for the code camp registration/session/speaker/etc data and organize a competition on the best created app. May be a panel with different attendees who attempted to build this application ?
  • Make sure the speakers are not running across the campus between their back-to-back talks. 

Check out some of the pictures:


And the complete photo album:

Looking forward to Silicon Valley Code Camp 6.0 next year!

Technorati: conf svcc javaee6 glassfish netbeans eclipse intellij cloud osgi servlets restful

Wednesday Aug 04, 2010

TOTD #143: Retrieve Twitter user timeline using using Jersey and OAuth

The Basic Authentication for authorizing with Twitter API will be turned off on Aug 16th. After that OAuth will be the only way to invoke the API.

Beginner's guide to OAuth provide an excellent explanation to OAuth. The typical analogy for OAuth is a "valet key" to the car which is a stripped down version of your regular key. These keys are meant for valet drivers who don't need to open trunk or glove compartment and don't need to drive the car for longer distance. So even though they have access to the entire car but are restricted to the limited functionality.

OAuth is used to share your resources (photos, videos, bank accounts, etc) stored on one site with another site without having to share your username and password. The site storing the resources is "Service Provider", the site requesting the access is "Consumer", you are the "User", "Tokens" are "valet key" that provide required access to the resources.

This Tip Of The Day (TOTD) explains how Jersey, the Reference Implementation for JAX-RS, provides seamless support for OAuth by creating a simple desktop application that retrieves user timeline on Twitter using OAuth. This blog is going to combine the instructions outlined in Understanding the guts of Twitter's OAuth for client apps and Using Jersey client OAuth support with Smugmug to achieve that.

Lets get started!

  1. Create a Maven project as:
    mvn -DarchetypeVersion=1.0 -DgroupId=org.glassfish.samples -DarchetypeArtifactId=maven-archetype-quickstart -Dversion=1.0-SNAPSHOT -DarchetypeGroupId=org.apache.maven.archetypes -Dpackage=org.glassfish.samples.twitter -DartifactId=twitter
    
  2. Update the generated "pom.xml" with the following fragments:
    <repositories>
      <repository>
        <id>glassfish-repository</id>
        <name>Java.net Repository for Glassfish</name>
        <url>http://download.java.net/maven/2/</url>
      </repository>
    </repositories>
    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-client</artifactId>
        <version>1.1.3-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-json</artifactId>
        <version>1.1.3-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>com.sun.jersey.oauth</groupId>
        <artifactId>oauth-signature</artifactId>
        <version>1.1.2-ea-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>com.sun.jersey.oauth</groupId>
        <artifactId>oauth-client</artifactId>
        <version>1.1.2-ea-SNAPSHOT</version>
       </dependency>
    </dependencies>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.0.2</version>
          <configuration>
            <source>1.6</source>
            <target>1.6</target>
          </configuration>
        </plugin>
      </plugins>
     </build>
    

    The Jersey dependencies add the core Jersey libraries and OAuth functionality in Jersey.
  3. Register your app with Twitter - Register your application with Twitter by clicking on Register a new application >>. The complete list of registered applications can be seen at Applications using Twitter. Select "Client" as the app type, select "Yes, use Twitter for login" and leave the "Callback URL" empty. The registration gives you "consumer key" and "consumer secret". These are used to obtain temporary credentials (or request tokens) from Twitter.
  4. Obtain Twitter OAuth credentials - Each OAuth request is an HTTP request with "Authorization" header specifying the information by OAuth service provider. Jersey provides a OAuthClientFilter to add this header to the outbound client request. Twitter API Wiki explains the authentication as multiple step process for desktop applications. Each step involves sending some parameters to twitter and getting a result back and the intent of each method/request is clearly explained in Understanding the guts of Twitter's OAuth for client apps. In our case, each request is created by using Jersey Client API and attaching OAuthClientFilter and is explained next.
    1. Request temporary credentials, a.k.a request token, from Twitter using oauth/request_token.
      1. In "App.java", create an instance of Jersey client in the constructor and attach a LoggingFilter to dump inbound/outbound messages as:
        public App() {
            // Create a Jersey client
            client = Client.create();
        
            client.addFilter(new LoggingFilter());
        }
        
      2. Request temporary credentials by adding the following method:
        public void getRequestToken() {
            client.removeAllFilters();
        
            // Create a resource to be used to make Twitter API calls
            WebResource resource = client.resource(REQUEST_TOKEN_URL);
        
            // Set the OAuth parameters
            OAuthSecrets secrets = new OAuthSecrets().consumerSecret(CONSUMER_SECRET);
            OAuthParameters params = new OAuthParameters().consumerKey(CONSUMER_KEY).
                    signatureMethod("HMAC-SHA1").version("1.0");
            // Create the OAuth client filter
            OAuthClientFilter oauthFilter =
                    new OAuthClientFilter(client.getProviders(), params, secrets);
        
            // Add the filter to the resource
            resource.addFilter(oauthFilter);
        
            // make the request and print out the result
            System.out.println(resource.get(String.class));
        }
        
        
        Note, "OAuthClientFilter" is used to populate the "Authorization" header instead of handcrafting it. The REQUEST_TOKEN_URL is "http://twitter.com/oauth/request_token", CONSUMER_SECRET and CONSUMER_KEY are the values obtained from registering your application.
      3. Edit "AppTest.java" and change "testApp" method such that it looks like:
        public void testApp() {
            App app = new App();
            app.getRequestToken();
        }
        
      4. Obtain the temporary credentials by running this application as:
        mvn test
        

        and see an output as:
        oauth_token=REQUEST_OAUTH_TOKEN&oauth_token_secret=REQUEST_OAUTH_TOKEN_SECRET&oauth_callback_confirmed=true
        

        REQUEST_OAUTH_TOKEN, a temporary token, is used to authorize on twitter.com.
    2. Authorize the user and obtain PIN
      1. Go to "https://twitter.com/oauth/authorize?oauth_token=REQUEST_OAUTH_TOKEN" in a browser window.
      2. If not already logged in, enter your twitter credentials and click "Allow".
      3. Copy the PIN.
    3. Request permanent credentials, a.k.a access token, from Twitter using oauth/access_token.
      1. Request permanent credentials by adding the following method in "App.java"
        public void getAccessToken() {
                client.removeAllFilters();
        
                // Set the OAuth parameters
                OAuthSecrets secrets = new OAuthSecrets().consumerSecret(CONSUMER_SECRET);
                OAuthParameters params = new OAuthParameters().consumerKey(CONSUMER_KEY).
                        signatureMethod("HMAC-SHA1").
                        version("1.0").
                        token(REQUEST_OAUTH_TOKEN).
                        verifier(PIN);
                // Create the OAuth client filter
                OAuthClientFilter oauthFilter =
                        new OAuthClientFilter(client.getProviders(), params, secrets);
        
                // Create a resource to be used to make Twitter API calls
                WebResource resource = client.resource(ACCESS_TOKEN_URL);
        
                // Add the filter to the resource
                resource.addFilter(oauthFilter);
        
                // make the request and print out the result
                System.out.println(resource.get(String.class));
            }
        
        REQUEST_OAUTH_TOKEN is the temporary token obtained earlier, ACCESS_TOKEN_URL is "https://twitter.com/oauth/access_token".

        Notice, REQUEST_OAUTH_TOKEN and PIN are now added to the OAuthClientFilter.
      2. Invoke this method by editing "AppTest.java" as:
        public void testApp() {
             App app = new App();
        //     app.getRequestToken();
             app.getAccessToken();
        }
        
      3. Obtain the permanent credentials by running this application as:
        mvn test
        

        and see an output as:
        oauth_token=ACCESS_OAUTH_TOKEN&oauth_token_secret=ACCESS_OAUTH_TOKEN_SECRET&user_id=USER_ID&screen_name=USER_NAME
        

        ACCESS_OAUTH_TOKEN is the authorized token that can be used for making any future requests, USER_ID and USER_NAME are identifiers for the user who signed in on twitter.com. 
  5. Get the last 20 status messages for the user from Twitter
    1. Add the following method in "App.java:
      public void getUserTimeline() {
          client.removeAllFilters();
      
          // Set the OAuth parameters
          OAuthSecrets secrets = new OAuthSecrets().consumerSecret(CONSUMER_SECRET);
          OAuthParameters params = new OAuthParameters().consumerKey(CONSUMER_KEY).
                  signatureMethod("HMAC-SHA1").
                  version("1.0").
                  token(ACCESS_OAUTH_TOKEN);
          // Create the OAuth client filter
          OAuthClientFilter oauthFilter =
                  new OAuthClientFilter(client.getProviders(), params, secrets);
      
          // Create a resource to be used to make Twitter API calls
          WebResource resource = client.resource(USER_TIMELINE_URL);
      
          // Add the filter to the resource
          resource.addFilter(oauthFilter);
      
         // Parse the JSON array
          JSONArray jsonArray = resource.get(JSONArray.class);
          List<String> statuses = new ArrayList<String>();
      
          try {
              for (int i = 0; i < jsonArray.length(); i++) {
                  JSONObject jsonObject = (JSONObject) jsonArray.get(i);
                  StringBuilder builder = new StringBuilder();
                  builder.append(jsonObject.getString("text")).
                          append(jsonObject.getString("created_at"));
                  statuses.add(builder.toString());
              }
          } catch (JSONException ex) {
              Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
          }
      } 
      USER_TIMELINE_URL is "http://api.twitter.com/1/statuses/user_timeline.json". The "getTimelineElements" method can be updated to pick other elements from the return JSON object. The complete JSON schema for the response is described here.
    2. Edit "AppTest.java" as:
      public void testApp() {
          App app = new App();
      //    app.getRequestToken();
      //    app.getAccessToken();
          app.getUserTimeline();
      }
      
    3. Finally get the last 20 status updates by giving the command:

      mvn test
      


      and see the output similar to:
      Running org.glassfish.samples.twitter.AppTest
      [Developing OSGi-Enabled Java EE Applications- http://bit.ly/aOim34 (via 
      @JavaOneConf) #javaone10Wed Aug 04 23:53:13 +0000 2010, Google Wave goes
       bye bye (via @google:)Update on Google Wave http://bit.ly/bIoDWAWed Aug
       04 21:16:07 +0000 2010, @gdaniels Yeah, I expected #wave to bye bye as
       well, but this is fairly quick!Wed Aug 04 21:15:41 +0000 2010,
      

And that's it!

This Tip Of The Day explained how to use Jersey to retrieve last 20 status messages that a user posted on twitter. Here are some other future possible additions:

  • POST status update
  • Integrate Search API using OAuth (is it possible ?)
  • Integrate Streaming API (need more investigation)
  • Create a web-base client that automatically redirects the user from application to twitter.com and then back to the application.

Jersey and OAuth wiki provides more details about how to use OAuth with Jersey.

Technorati: totd jaxrs jersey restful webservices oauth twitter glassfish

Monday Dec 31, 2007

Screencast #Web11: Travel Map - Another Real-life app using jMaki & Jersey

In my role of Technology Evangelist, I get the opportunity to meet a lot of community (folks like you :) all around the world. In the year 2007, I represented GlassFish (and related technologies - Metro, jMaki and Jersey) at multiple conferences. This blog introduces a  new real-life application that plots all the places I visited this year on a jMaki-wrapped Google Map widget. Clicking on the marker shows more information about the event such as dates and the blog entry covering the event.

Play the video below to see how the application looks like.

Here is the architecture of this application:

travel map architecture

It consists of a server-side and a client-side applications - developed as NetBeans projects.

  1. Server-side project - A RESTful Web service endpoint that provides resource represenations for all the events attended and associated meta information such as date and blog URLs. This endpoint is created using Jersey.
  2. Client-side project - A jMaki-enabled Web application that consumes the representations generated by the RESTful Web service and plots the information on a jMaki-wrapped Google Map widget.

Both the server-side and client-side are deployed on GlassFish.

This is only a sample application so optimizations are certainly possible and corner cases (such as no blog entry for a particular visit) are not accounted for. But the application still demonstrates the concept. The fully built application looks like as shown below:

Arun's Travel Map 2007

My first presentation in this role was Sun Tech Days Atlanta (highlighted in the image). This application generates an interactive Google Map so feel free to zoom in/out and click 

And one last thing before we build the application. Here is the list of technologies and associated concepts used to build this application:

  1. Jersey
    1. Shows an example of how RESTful Web services can be easily generated from JPA Entity Classes.
    2. Shows how all the resource representations (instead of reference to individual resources) can be returned by a Jersey endpoint.
  2. jMaki
    1. Shows how to consume XML data from an external service (RESTful Web service endpoint) in this case.
    2. Shows how the underlying data model of a widget (Google Map in this case) can be accessed and manipulated.
  3. GlassFish
    1. All the applications are deployed on GlassFish - implicit in the development/deplyment process through seamless integration with NetBeans.
  4. NetBeans 6
    1. Used for generation of RESTful Web services from JPA Entity Classes.
    2. Used for generating/deploying jMaki projects and drag-and-drop of jMaki-wrapped widgets.
  5. JavaScript Closures - to persist the state for asynchronous callback functions
  6. JavaScript DOM processing - to process the XML data received from Jersey endpoint.
  7. Google Maps API
    1. Generate meaningful markers on each location
    2. Populate Google Map from a RESTful Web service endpoint
  8. Java Persistence API - to retrieve data from the database.

And finally, lets build this application. Lets build the RESTful Web service endpoint project first.

  1. Create and Populate the Database
    1. In the NetBeans IDE, go to Services tab, and connect to the database with URL "jdbc:derby://localhost:1527/sample [app on APP]" (right-click and select "Connect...").
    2. Right-click on this database and select "Execute Command..." and create a table by giving the following command:

      create table EVENTS (id int GENERATED ALWAYS AS IDENTITY,
                          event_name varchar(255),
                          dates varchar(20),
                          venue varchar(255),
                          blogs varchar(2056),
                          PRIMARY KEY (id))


      Notice, the "id" column is marked as IDENTITY that instructs the database to auto generate the values for this column and increment by 1 (default) for each row. This column is also marked as the primary key.
    3. Again right-click on the database and select "Execute Command..." to add data to the table by giving the following command:

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('SunTech Days - Atlanta', 'Jan 16 - Jan 17', 'Cobb Galleria Center, Two Galleria Parkway, Atlanta, Georgia, 30339', 'http://blogs.sun.com/arungupta/entry/wsit_and_web_2_0');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('jMaki Day', ' Feb 23', '4150 Network Circle Santa Clara, CA 95054', 'http://blogs.sun.com/arungupta/entry/sun_internal_jmaki_day_review');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Ajax World - New York', 'Mar 19 - Mar 21', 'The Roosevelt Hotel, 45 E 45th St, New York, NY 10017', 'http://blogs.sun.com/arungupta/entry/sun_ajax_world');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('The Server Side Java Symposium - Las Vegas', 'Mar 22', '3355 Las Vegas Blvd. South Las Vegas, NV 89109', 'http://blogs.sun.com/arungupta/entry/sun_the_server_side_java, http://blogs.sun.com/arungupta/entry/tango_at_venetian_las_vegas');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('JavaOne - San Francisco', 'May 7 - May 11', 'Moscone Center, 747 Howard Street, San Francisco, CA 94103', 'http://blogs.sun.com/arungupta/entry/slides_for_ts_4865, http://blogs.sun.com/arungupta/entry/javaone_2007_day_1_finished, http://blogs.sun.com/arungupta/entry/javaone_2007_day_1, http://blogs.sun.com/arungupta/entry/javascript_everywhere_javaone_2007_demo, http://blogs.sun.com/arungupta/entry/excel_using_wsit_javaone_2007, http://blogs.sun.com/arungupta/entry/ts_4865_takes_two_to, http://blogs.sun.com/arungupta/entry/communityone_glassfish_day_report, http://blogs.sun.com/arungupta/entry/javaone_2007_backstage, http://blogs.sun.com/arungupta/entry/javaone_2007_is_almost_here, http://blogs.sun.com/arungupta/entry/my_javaone_2007_picks');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Rails Conf - Portland', 'May 17 - May 20', '777 NE MLK, Jr. Blvd. Portland, OR 97232', 'http://blogs.sun.com/arungupta/entry/tim_bray_s_keynote_session, http://blogs.sun.com/arungupta/entry/sun_rails_conf_2007_keep, http://blogs.sun.com/arungupta/entry/getting_started_with_jruby_tutorial, http://blogs.sun.com/arungupta/entry/jmaki_netbeans_and_glassfish_in');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Google Developer Day - San Jose', 'May 31', '150 W San Carlos St San Jose, CA 95113', 'http://blogs.sun.com/arungupta/entry/google_developer_day_report');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Mashup Camp - Mountain View', 'Jul 18 - Jul 19', 'Computer History Museum, 1401 N Shoreline Blvd., Mountain View, CA 94043', 'http://blogs.sun.com/arungupta/entry/jmaki_at_mashup_camp_report, http://blogs.sun.com/arungupta/entry/jmaki_mashup_camp');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('OSCON - Portland', 'Jul 23 - Jul 27', '777 NE MLK, Jr. Blvd. Portland, OR 97232', 'http://blogs.sun.com/arungupta/entry/jmaki_oscon');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('JRuby Hack Day - San Francisco', 'Aug 8', '1201 8th St, San Francisco, CA 94107', 'http://blogs.sun.com/arungupta/entry/jruby_on_rails_hackday_report, http://blogs.sun.com/arungupta/entry/learn_jruby_on_rails_free');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Rich Web Experience - San Jose', 'Sep 6 - Sep 8', '170 S Market St, San Jose, CA 95113', 'http://blogs.sun.com/arungupta/entry/the_rich_web_experience_2007, http://blogs.sun.com/arungupta/entry/jmaki_javafx_the_rich_web');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Rails Conf Europe - Berlin', 'Sep 17 - Sep 19', 'Maritim Pro Arte, Friedrichstrasse 151, 10117 Berlin', 'http://blogs.sun.com/arungupta/entry/rails_conf_europe_2007_day2, http://blogs.sun.com/arungupta/entry/rails_conf_europe_2007_day1, http://blogs.sun.com/arungupta/entry/rails_conf_europe_2007_day, http://blogs.sun.com/arungupta/entry/jmaki_netbeans_and_glassfish_in1');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Sun Tech Days - Rome', 'Sep 24 - Sep 25', 'Meliá Roma Aurelia Antica, Vía Aldobrandeschi, 223  Rome ITALY  00163', 'http://blogs.sun.com/arungupta/entry/netbeans_day_rome_2007, http://blogs.sun.com/arungupta/entry/travel_tips_to_rome, http://blogs.sun.com/arungupta/entry/glassfish_metro_jersey_and_jmaki');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Sun Tech Days - Milan', 'Sep 26 - Sep 28', 'ATA Hotel Quark - Via Lampedusa 11/a 20141 Milano, Italia', 'http://blogs.sun.com/arungupta/entry/glassfish_day_milan_2007');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Mid West Java Tech Days - Minneapolis', 'Oct 16', 'University of St Thomas, MPL 201, 1000 LaSalle Avenue, Minneapolis, MN 55403-2005', 'http://blogs.sun.com/arungupta/entry/mid_west_java_tech_days, http://blogs.sun.com/arungupta/entry/metro_and_jmaki_in_minneapolis');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Mid West Java Tech Days - Chicago', 'Oct 18', 'Donald E Stephens Convention Center, 9301, W Bryn Mawr Ave, Rosemont IL 60018', 'http://blogs.sun.com/arungupta/entry/mid_west_java_tech_days1, http://blogs.sun.com/arungupta/entry/crowne_plaza_chicago_o_hare, http://blogs.sun.com/arungupta/entry/metro_and_jmaki_in_minneapolis');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Silicon Valley Code Camp - Los Altos', 'Oct 27', 'Foothill College, Los Altos, CA', 'http://blogs.sun.com/arungupta/entry/silicon_valley_code_camp_trip, http://blogs.sun.com/arungupta/entry/metro_jmaki_silicon_valley_code');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Sun Tech Days - Beijing', 'Nov 1 - Nov 3', 'Beijing International Convention Center, No.8 Beichendong Road Chaoyang District, Beijing', 'http://blogs.sun.com/arungupta/entry/glassfish_day_beijing_2007_by, http://blogs.sun.com/arungupta/entry/wangfujing_street_authentic_china_in, http://blogs.sun.com/arungupta/entry/sun_tech_days_beijing_talent, http://blogs.sun.com/arungupta/entry/sun_tech_days_beijing_day, http://blogs.sun.com/arungupta/entry/travel_tips_to_beijing, http://blogs.sun.com/arungupta/entry/glassfish_day_beijing');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner Preso - Toronto', 'Nov 21', 'Toronto City Center', 'http://blogs.sun.com/arungupta/entry/metro_jmaki_jruby_glassfish_q');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner Preso - Montreal', 'Nov 21', 'Montreal City Center', 'http://blogs.sun.com/arungupta/entry/metro_jmaki_jruby_glassfish_q');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('GlassFish - Delhi University', 'Dec 3', 'New Delhi', 'http://blogs.sun.com/arungupta/entry/glassfish_delhi_university');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('FOSS.IN - Bangalore', 'Dec 4', 'India Institute of Science, Bangalore', 'http://blogs.sun.com/arungupta/entry/packaging_java_apps_for_ubuntu, http://blogs.sun.com/arungupta/entry/foss_in_schedules_now_available, http://blogs.sun.com/arungupta/entry/glassfish_foss_in_2007');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner Preso - Bangalore', 'Dec 4', 'Bangalore', 'http://blogs.sun.com/arungupta/entry/glassfish_bangalore_chennai_and_pune');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner Preso - Chennai', 'Dec 5', 'Chennai', 'http://blogs.sun.com/arungupta/entry/glassfish_bangalore_chennai_and_pune');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner Preso - Pune', 'Dec 6', 'Pune', 'http://blogs.sun.com/arungupta/entry/glassfish_bangalore_chennai_and_pune');
      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner Preso - San Francisco', 'Dec 17', 'San Francisco', 'http://blogs.sun.com/arungupta/');

      These SQL statements populate the database with details about my visits in 2007. If you'd like to develop a similar application highlighting your visits then you'll need to modify the VALUES clause to match accordingly.
  2. Create and Configure RESTful Web service endpoint
    1. Create a Persistence Unit as described in "Generating Entity Classes from Database" section in Getting Started with RESTful Web Services. Lets say the project name is "WebApplication3", package name is "events" and the table name to generate Entity classes is EVENTS. Take everything else as the defaults.
    2. Generate a RESTful Web service as described in "Generating RESTful Web Services from Entity Classes" section in Getting Started with RESTful Web Services.
      1. Add a new class EventsList in the events package as:

        @javax.xml.bind.annotation.XmlRootElement
        public class EventsList {
          @javax.xml.bind.annotation.XmlElement
          protected java.util.List<Events> events;

          public EventsList() {
            if (events == null)
              events = new java.util.ArrayList<Events>();
          }

          public void add(Events name) {
            events.add(name);
          }

          public java.util.List<Events> getValue() {
            return events;
          }
        }
      2. In service.EventsResource, change the method associated with GET to:

        public EventsList get() {
                EventsList eventsList = new EventsList();
                List<Events> list  = PersistenceService.getInstance().createQuery("SELECT e FROM Events e").getResultList();
                for (Events e : list) {
                    eventsList.add(e);
                }
                return eventsList;
        }

        This will ensure that all the resource representations are returned instead of a reference to the resource. Make sure to fix the imports.
That's it, our server-side project is now ready. "http://localhost:8080/WebApplication3/resources/events" now return a complete RESTful representation of all the rows from the database table EVENTS.

Lets build the client-side application next. Make sure jMaki plug-in in NetBeans IDE is already installed.
  1. In the NetBeans IDE, create a new Web project, enable "Ajax Framework" and choose the "Standard" layout for "index.jsp".  Lets say the project name is "WebApplication4".
  2. Drag-and-drop a jMaki-wrapped Google Map widget in the 'Main Content Area' and jMaki-wrapped Yahoo Button in the 'Sidebar Content Here'.
  3. Customise the widgets
    1. Add id="mymap" attribute to the Google Map widget. The updated widget looks like as shown below:

      <a:widget  name="google.map" id="mymap"
               args="{ centerLat : 37.4041960114344,
                       centerLon : -122.008194923401 }" />

      id="mymap"
      will allow the Map widget to be accessed by name later.
    2. Add args="{label:'Plot Events'}" attribute to thes Yahoo button widget. The updated widget looks like as shown below:

      <a:widget name="yahoo.button" args="{label:'Plot Events'}"/>
  4. In glue,js, add the following code to \*onClick subscribe method:

    var url = jmaki.xhp + "?id=events";
    var _map = jmaki.getWidget("mymap").map;
    _map.setZoom(2);
    _map.clearOverlays();
    _map.enableInfoWindow();
       
    jmaki.doAjax({method: "GET",
        url: url,
        callback: function(_req) {          
            var xmlobject = (new DOMParser()).parseFromString(_req.responseText, "text/xml");
            var root = xmlobject.getElementsByTagName('eventsList')[0];
            var events = root.getElementsByTagName('events');
            for (var i = 0 ; i < events.length ; i++) {
                var event = events[i];
                var eventName = event.getElementsByTagName('eventName')[0].firstChild.nodeValue;
                var venue = event.getElementsByTagName('venue')[0].firstChild.nodeValue;
                var blogs = event.getElementsByTagName('blogs')[0].firstChild.nodeValue;
                var dates = event.getElementsByTagName('dates')[0].firstChild.nodeValue;
               
    var id = event.getElementsByTagName('id')[0].firstChild.nodeValue;
                   
                var encodedLocation = encodeURIComponent("location=" + venue);
                var url = jmaki.xhp + "?id=yahoogeocoder&urlparams=" + encodedLocation;
                jmaki.myHandler(url, eventName, blogs, dates, id, _map);
            }
        }
    });
  5. Add the following functions above the \*onClick subscribe method:

    // "Function closure" used from http://econym.googlepages.com/basic1.htm
    // Creates local copy of "marker" and "html" variables to be preserved for later use
    function createMarker(point,html) {
        var marker = new GMarker(point);
        GEvent.addListener(marker, "click", function() {
            marker.openInfoWindowHtml(html);
        });
        return marker;
    };

    // Function closure that preserves "eventName", "blogs", "dates and "id"
    // Gets the latitude/longitude from Yahoo Geocoding service and plots them on the map
    // Also creates meaningful markers
    jmaki.myHandler = function(_url, eventName, blogs, dates, id, _map) {
        jmaki.doAjax({url: _url,
            callback : function(req) {
                if (req.responseText.length > 0) {
                    jmaki.log("name: " + eventName);
                    var response = eval("(" + req.responseText + ")");
                    var coordinates = response.coordinates;
                    jmaki.publish("/jmaki/plotmap", coordinates);
                    jmaki.log("plotting " + eventName);
                    var latlng = new GLatLng(coordinates[0].latitude, coordinates[0].longitude);
                   
                    var blogHtml = "";
                    b = blogs.split(', ');
                    for (i=0; i<b.length; i++) {
                        blogHtml += '<a href="' + b[i] + '">' + (i+1) + '</a>';
                        if (i<b.length-1)
                            blogHtml += ", ";
                    }
                   
                    var txt = '<table>' +
                    '<tr><td>#' + id + ": " + eventName + '</td></tr>' +
                    '<tr>Dates: ' + dates + ', 2007</td></tr>' +
                    '<tr><td>Blogs: ' + blogHtml + '</td></tr>' +
                    '</table>';
                   
                    var marker = createMarker(latlng, txt);
                    _map.addOverlay(marker);
                    marker.openInfoWindowHtml(txt);
                } else {
                    jmaki.log("Failed to get coordinates for " + location );
                }
            }
        });   
    };
  6. Add the following entry in Web Pages, resources, xhp.json:

    ,
    {"id": "events",
     "url":"http://localhost:8080/WebApplication3/resources/events/"
    }


    assuming WebApplication3 is the project where RESTful Web service endpoint is hosted.
That completes our client-side web application as well. Now, either hit F6 (default key to Run the NetBeans project) and this will show http://localhost:8080/WebApplication4/index.jsp in the configured browser. Once you click on "Plot Events" button, all the markers on the Google Map are plotted.
Future Improvements
  1. If Jersey can return all the resource representations directly, then the workaround used above may not be required.
  2. Use e4x after <script type="text/javascript"> in index.jsp can be generated as <script type="text/javascript; e4x=1">.
  3. Build the client-side application using Rails once issue #309 & #310 are resolved.
    1. Once deployed as Rails application on WEBrick, create a WAR file and deploy on GlassFish.
    2. Try this application using GlassFish v3 gem.
  4. Embed Google Map in the blog entry.
An alternate title of this blog entry could've been "How I spent my winter break ?". But in order to keep the title inline with rest of other entries (keeping it simple and reflecting the content of the entry) I decided to use the existing title ;-)

Technorati: screencast conf jmaki jersey netbeans glassfish jpa javascript googlemaps restful web2.0 jmakimashups

About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today