The Source for Java Technology Collaboration
User: Password:



Marc Hadley's Blog

Marc Hadley Dr. Marc J. Hadley is a Senior Staff Engineer in the Office of the CTO, Sun Microsystems where he works on a variety of Web-based technologies. He is currently co-spec lead for JSR 311, a Java API for RESTful Web Services. Previously Marc represented Sun in the W3C XML Protocol and W3C Web Services Addressing working groups where he was co-editor of the SOAP 1.2 and WS-Addressing specifications. Marc also served as the technical lead for Sun's participation at the Web Services Interoperability Organisation (WS-I) and was co-spec lead for JSR 224 (JAX-WS) at the JCP.



JAX-RS Public Review Draft and JavaOne

Posted by mhadley on May 02, 2008 at 12:44 PM | Permalink | Comments (3)

The JAX-RS public review draft is now available for, er, review - download it here.

If you are attending JavaOne we'd love to see you at any or all of the following sessions:

I'll also be on Glen's panel "REST Versus SOA: Can We All Just Get Along?" which follows our BOF in the same room at 21:30 (Tuesday is shaping up to be a busy day).



JAX-RS Implementations

Posted by mhadley on April 01, 2008 at 01:20 PM | Permalink | Comments (2)

The Restlet team just announced a new release. Amongst the new features is support for JAX-RS, see an example here. With the RI (Jersey), Apache CXF, and JBoss RESTeasy, that makes a total of four implementations currently underway. The feedback we're receiving as a result of these parallel implementations is proving very useful and I'm grateful for the all the good input we've been getting.

The graph below shows the monthly totals for emails sent to the dev, users, issues and commits mailing lists for both JAX-RS and Jersey.

activity.png

As you can see, traffic on the lists has picked up quite a bit over the last few months and a good proportion of the increase is due to the additional implementation work flushing out issues. We've also been working through our issues list with a view to publishing a JCP public review draft in the not too distant future.



Authentication in Jersey

Posted by mhadley on March 07, 2008 at 01:14 PM | Permalink | Comments (3)

I'm working on an internal project building some RESTful services using a combination of Jersey, JPA, Glassfish and Derby. Actions on some resources require authentication and I need access to the name of the authenticated user in the resource method. This entry describes the steps to set this up - kudos to my colleague Hubert for working out much of the below.

Obviously you're going to need all the ingredients listed above. I'm using NetBeans which came bundled with Glassfish and Derby so all I had to do was install the RESTful Web Services plug-in, YMMV.

Next you need a database to store username, password and group information. Here's the table definitions I used:

CREATE TABLE users (
    id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(64) UNIQUE NOT NULL,
    password VARCHAR(64) NOT NULL,
);
CREATE INDEX username ON users(username);

CREATE TABLE groups (
    id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(64) NOT NULL REFERENCES users(username) ON DELETE CASCADE,
    groupname VARCHAR(64)
);

Depending on your needs you could lose the integer fields, the important thing is to have a column in the group table with the same name as the column in the users table that holds the username. Those two columns are used to join the users table to the groups table. The Glassfish security realm is going to execute a query like this to retrieve the groups (which map to security roles) that a particular user is a member of:

SELECT groupname FROM groups g, users u where g.username = u.username and u.username = ?

Add some users and groups for testing purposes, note that the password should be an md5 hash, not plaintext - there are several web sites that offer online MD5 generators that you can use. I used groups named USERS and ADMINISTRATORS, you'll see where these come in later.

Next you need to define a JDBC data source, connection pool and security realm in Glassfish. The first two are pretty straightforward and will likely be taken care of already if you have deployed a web application using the database. Setting up the security realm is also straightforward once you have a suitable database structure as described above. You do this via the admin console as shown below (there are a couple of additional fields on the form, you can leave those blank):

realm.png

Its now possible to require authentication for access to a JAX-RS resource via web.xml. Say I have a resource that only authenticated users can use, e.g.:

@Path("dropbox")
public class DropBox {
    @Context
    SecurityContext security;
    
    @POST
    public Response drop(InputStream data) {
        String username = security.getUserPrincipal().getName();
        ...
    }
}

Note the use of dependency injection to get an instance of SecurityContext and how that interface provides access to the username. In order for this to work we have to tell the container that authentication is required for access to the /dropbox URI. Add the following lines to your web.xml to restrict use of this resource to members of the USERS group:

<security-constraint>
    <display-name>DropBox</display-name>
    <web-resource-collection>
        <web-resource-name>DropBox</web-resource-name>
        <description></description>
        <url-pattern>/dropbox</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
        <http-method>HEAD</http-method>
        <http-method>PUT</http-method>
        <http-method>OPTIONS</http-method>
        <http-method>TRACE</http-method>
        <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
        <description>Have to be a USER</description>
        <role-name>USERS</role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>userauthn</realm-name>
</login-config>
<security-role>
    <description/>
    <role-name>USERS</role-name>
</security-role>

That about it, after you redeploy the application, the next time you try to access /dropbox you'll get a 401 Unauthorized response unless you include a suitable Authorization header in the request.



Integrating Jersey and Abdera

Posted by mhadley on February 05, 2008 at 11:38 AM | Permalink | Comments (4)

I'm working on an internal project that involves adding Atom Publishing Protocol support to a data store. Naturally, I'm using Jersey for the HTTP side of things and decided to give Apache Abdera a try for simplifying working with feeds and entries.

With JAX-RS I can write a feed resource pretty easily:

import java.net.URI;
import java.util.Date;
import javax.ws.rs.ConsumeMime;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.UriParam;
import javax.ws.rs.core.HttpContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;

@ProduceMime("application/atom+xml")
@ConsumeMime("application/atom+xml")
@Path("myfeed")
public class FeedResource {
   @HttpContext
   private UriInfo uriInfo;

   @GET
   public Feed getFeed() {
       Feed f = AbderaSupport.getAbdera().getFactory().newFeed();
       f.setTitle("...");
       f.setId(...);
       f.addAuthor(...);
       f.setUpdated(...);
       URI feedLink = uriInfo.getRequestUri();
       f.addLink(feedLink.toString(),"self");
       for (...) {
           Entry e = f.addEntry();
           URI entryLink = ...
	       f.addLink(entryLink.toString(),"alternate");
           ...
       }
       return f;
   }

   @POST
   public Response addEntry(Entry e) {
       Entry newEntry = AbderaSupport.getAbdera().newEntry();
       URI entryLink = ...;
       ...
       return Response.created(entryLink).entity(newEntry).build();
   }
}

The getFeed method creates an Abdera Feed instance and returns it in response to a GET request on the myfeed URI. The addEntry method is called with an Abdera Entry instance that corresponds to the body of a POST request. It does whatever is necessary to persist the new entry and then creates and returns a 201 Created response with a Location header and a new Entry corresponding to the persisted entry as the body of the response.

To allow use of Abdera's Entry and Feed types in resource method arguments and return types I had to implement the JAX-RS interfaces MessageBodyReader and MessageBodyWriter for those types. This turned out to be quite simple with Abdera:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.apache.abdera.Abdera;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;

@Provider
@ProduceMime("application/atom+xml")
@ConsumeMime("application/atom+xml")
public class AbderaSupport implements MessageBodyWriter<Object>, 
       MessageBodyReader<Object> {

   private final static Abdera abdera = new Abdera();

   public static Abdera getAbdera() {
       return abdera;
   }

   public long getSize(Object arg0) {
       return -1;
   }

   public boolean isWriteable(Class<?> type) {
       return (Feed.class.isAssignableFrom(type) || Entry.class.isAssignableFrom(type));
   }

   public void writeTo(Object feedOrEntry, MediaType mediaType, 
           MultivaluedMap<String, Object> headers, 
           OutputStream outputStream) throws IOException {
       if (feedOrEntry instanceof Feed) {
           Feed feed = (Feed)feedOrEntry;
           Document<Feed> doc = feed.getDocument();
           doc.writeTo(outputStream);
       } else {
           Entry entry = (Entry)feedOrEntry;
           Document<Entry> doc = entry.getDocument();
           doc.writeTo(outputStream);
       }
   }

   public boolean isReadable(Class<?> type) {
       return (Feed.class.isAssignableFrom(type) || Entry.class.isAssignableFrom(type));
   }

   public Object readFrom(Class<Object> feedOrEntry, MediaType mediaType, 
           MultivaluedMap<String, String> headers, 
           InputStream inputStream) throws IOException {
       Document<Element> doc = getAbdera().getParser().parse(inputStream);
       Element el = doc.getRoot();
       if (feedOrEntry.isAssignableFrom(el.getClass())) {
           return el;
       } else {
           throw new IOException("Unexpected payload, expected "+feedOrEntry.getName()+
               ", received "+el.getClass().getName());
       }
   }
}

Notice I actually implemented MessageBodyReader<Object> rather than MessageBodyReader<Entry> and MessageBodyReader<Feed>. This allowed me to support both types in the same class. Same for MessageBodyWriter. The isReadable and isWriteable methods allow a class to reject types it doesn't support.

Abdera is shaping up to be a nice API for working with Atom documents. Hopefully the above demonstrates how easy it is to integrate support for new formats into Jersey.



May 2008
Sun Mon Tue Wed Thu Fri Sat
        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 31


Search this blog:
  

Categories
Community: Java Web Services and XML
Community: Mac Java Community
Distributed
J2EE
J2SE
Programming
Web Services and XML
Archives

May 2008
April 2008
March 2008
February 2008
December 2007
November 2007
September 2007
August 2007
June 2007
April 2007
March 2007
February 2007
December 2006
October 2006
September 2006
August 2006
June 2006
May 2006
April 2006
March 2006
January 2006
May 2005
April 2005
February 2005
August 2004
June 2004
April 2004
October 2003
July 2003

Recent Entries

JAX-RS Public Review Draft and JavaOne

JAX-RS Implementations

Authentication in Jersey



Powered by
Movable Type 3.01D


 Feed java.net RSS Feeds