Showing posts with label REST. Show all posts
Showing posts with label REST. Show all posts

Wednesday, January 29, 2014

Selecting level of detail returned by varying the content type, part II

In my previous entry, we looked at using the feature of MOXy to control the level of data output for a particular entity. This post looks at an abstraction provided by Jersey 2.x that allows you to define a custom set of annotations to have the same effect.

As before we have an almost trivial resource that returns an object that Jersey will covert to JSON for us, note that for the moment there is nothing in this code to do the filtering - I am not going to pass in annotations to the Response object as in the Jersey examples:

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("hello")
public class SelectableHello {


  @GET
  @Produces({ "application/json; level=detailed", "application/json; level=summary", "application/json; level=normal" })
  public Message hello() {

    return new Message();

  }
}

In my design I am going to define four annotations: NoView, SummaryView, NormalView and DetailedView. All root objects have to implement the NoView annotation to prevent un-annotated fields from being exposed - you might not feel this is necessary in your design. All of these classes look the same so I am going to only show one. Note that the factory method creating a AnnotationLiteral has to be used in preference to a factory that would create a dynamic proxy to have the same effect. There is code in 2.5 that will ignore any annotation implemented by a java.lang.reflect.Proxy object, this includes any annotations you may have retrieved from a class. I am working on submitting a fix for this.

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.enterprise.util.AnnotationLiteral;

import org.glassfish.jersey.message.filtering.EntityFiltering;

@Target({ ElementType.TYPE, 
  ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EntityFiltering
public @interface NoView {

  /**
   * Factory class for creating instances of the annotation.
   */
  public static class Factory extends AnnotationLiteral<NoView> 
    implements NoView {

    private Factory() {
    }

    public static NoView get() {
      return new Factory();
    }
  }

}

Now we can take a quick look at our Message bean, this is slightly more complicated than my previous example to showing filtering of subgraphs in a very simple form. As I said before the class is annotated with a NoView annotation at the root - this should mean that the privateData is never returned to the client as it is not specifically annotated.

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@NoView
public class Message {


  private String privateData;
  @SummaryView
  private String summary;
  @NormalView
  private String message;
  @DetailedView
  private String subtext;
  @DetailedView
  private SubMessage submessage;

  public Message() {
    summary = "Some simple summary";
    message = "This is indeed the message";
    subtext = "This is the deep and meaningful subtext";
    submessage = new SubMessage();
    privateData = "The fox is flying tonight";
  }


  // Getters and setters not shown
}


public class SubMessage {

  private String message;

  public SubMessage() {
    message = "Some sub messages";
  }

  // Getters and setters not shown
}


As noted before there is no code in the resource class to deal with filtering - I considered this to be a cross cutting concern so I have abstracted this into a WriterInterceptor. Note the exception thrown if a entity is used that doesn't have the NoView annotation on it.

import java.io.IOException;

import java.lang.annotation.Annotation;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.ws.rs.ServerErrorException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

@Provider
public class ViewWriteInterceptor implements WriterInterceptor {


  private HttpHeaders httpHeaders;

  public ViewWriteInterceptor(@Context HttpHeaders httpHeaders) {
    this.httpHeaders = httpHeaders;
  }


  @Override
  public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) 
    throws IOException,
           WebApplicationException {

    // I assume this case will never happen, just to be sure
    if (writerInterceptorContext.getEntity() == null) {

      writerInterceptorContext.proceed();
      return;
    }
    else
    {
      Class<?> entityType = writerInterceptorContext.getEntity()
          .getClass();
      String entityTypeString = entityType.getName();
      
      // Ignore any Jersey system classes, for example wadl
      //
      if (entityType == String.class  || entityType.isArray() 
        || entityTypeString.startsWith("com.sun") 
        || entityTypeString.startsWith("org.glassfish")) {
        writerInterceptorContext.proceed();
        return;
      }
      
      // Fail if the class doesn't have the default NoView annotation 
      // this prevents any unannotated fields from showing up
      //
      else if (!entityType.isAnnotationPresent(NoView.class)) {
        throw new ServerErrorException("Entity type should be tagged with @NoView annotation " + entityType, Response.Status.INTERNAL_SERVER_ERROR);
        
      }
    }

    // Get hold of the return media type:
    //

    MediaType mt = writerInterceptorContext.getMediaType();
    String level = mt.getParameters().get("level");

    // Get the annotations and modify as required
    //

    Set<Annotation> current = new LinkedHashSet<>();
    current.addAll(Arrays.asList(
      writerInterceptorContext.getAnnotations()));

    switch (level != null ? level : "") {
      default:
      case "detailed":
        current.add(com.example.annotation.DetailedView.Factory.get());
      case "normal":
        current.add(com.example.annotation.NormalView.Factory.get());
      case "summary":
        current.add(com.example.annotation.SummaryView.Factory.get());

    }

    writerInterceptorContext.setAnnotations(
      current.toArray(new Annotation[current.size()]));

    //

    writerInterceptorContext.proceed();
  }
}

Finally you have to enable the EntityFilterFeature manually, to do this you can simple register it in your Application class

import java.lang.annotation.Annotation;

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/resources/")
public class SelectableApplication extends ResourceConfig {
  public SelectableApplication() {

    packages("...");

    // Set entity-filtering scope via configuration.
    property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE, 
    new Annotation[] {
      NormalView.Factory.get(), DetailedView.Factory.get(), 
      NoView.Factory.get(), SummaryView.Factory.get()
    });
    register(EntityFilteringFeature.class);
  }


}

Once you have this all up and running the application will respond as before:

GET .../hello Accept application/json; level=detailed or application/json
{
  "message" : "This is indeed the message",
  "submessage" : {
    "message" : "Some sub messages"
  },
  "subtext" : "This is the deep and meaningful subtext",
  "summary" : "Some simple summary"
}

GET .../hello Accept application/json; level=normal
{
  "message" : "This is indeed the message",
  "summary" : "Some simple summary"
}


GET .../hello Accept application/json; level=summary
{
  "summary" : "Some simple summary"
}

This is feel is a better alternative to using the MOXy annotations directly - using custom annotations should have to much easier to port your application to over implementation even if you have to provide you own filter. Finally it is worth also exploring the Jersey extension to this that allows Role based filtering which I can see as being useful in a security aspect.

Tuesday, January 28, 2014

Selecting level of detail returned by varying the content type

So I have been playing with the various ways of generating JSON in Jersey this week and I have been looking at solutions to the problem of returning different levels of detail depending on the client requirements. Here is one solution that uses the MoXY JAX-B provider in Jersey 2.x.

Consider this very simple hello world class:

@Path("hello")
public class SelectableHello {
    
    @GET
    @Produces({"application/json; level=detailed", "application/json; level=summary", "application/json; level=normal"})
    public Message hello() {
        return new Message();
    }
}

The Message class is annotated with some special annotations from MOXy that allows you to specify different partial object graphs. So my very simple message class looks like the following with three different levels defined:

import javax.xml.bind.annotation.XmlRootElement;

import org.eclipse.persistence.oxm.annotations.XmlNamedAttributeNode;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraph;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraphs;


@XmlNamedObjectGraphs({
  @XmlNamedObjectGraph(name = "summary", 
    attributeNodes = { 
      @XmlNamedAttributeNode("summary") }),
  @XmlNamedObjectGraph(name = "normal",
    attributeNodes =
    { @XmlNamedAttributeNode("summary"), 
      @XmlNamedAttributeNode("message") }),
  @XmlNamedObjectGraph(name = "detailed",
    attributeNodes =
    { @XmlNamedAttributeNode("summary"), 
      @XmlNamedAttributeNode("message"),
      @XmlNamedAttributeNode("subtext") })
  })
@XmlRootElement
public class Message {

  private String summary, message, subtext;

  public Message() {
    summary = "Some simple summary";
    message = "This is indeed the message";
    subtext = "This is the deep and meaningful subtext";
  }

  public void setSummary(String summary) {
    this.summary = summary;
  }

  public String getSummary() {
    return summary;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public String getMessage() {
    return message;
  }

  public void setSubtext(String subtext) {
    this.subtext = subtext;
  }

  public String getSubtext() {
    return subtext;
  }


}

Then it is a relatively easy step to define a JAX-RS ContextResolver that returns a suitably configured MoxyJsonConfig depending on the mime type. The code in here is a little bit ropey as it assumes that the first item in the accept list is the correct one.

import javax.ws.rs.core.Context;

import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import org.eclipse.persistence.jaxb.MarshallerProperties;

import org.glassfish.jersey.moxy.json.MoxyJsonConfig;

@Provider
public class MoxyJsonConfigResolver implements ContextResolver<MoxyJsonConfig> {

    HttpHeaders httpHeaders;


    @Context
    public void setHeaders(HttpHeaders httpHeaders) {
        this.httpHeaders = httpHeaders;
    }

    @Override
    public MoxyJsonConfig getContext(Class<?> c) {

        // Assume we are going to match the first accept
        //
        MediaType responseType = httpHeaders.getAcceptableMediaTypes().get(0);

        MoxyJsonConfig config = new MoxyJsonConfig();
        String level = responseType.getParameters().get("level");
        config.getMarshallerProperties().put(MarshallerProperties.OBJECT_GRAPH, level != null ? level : "detailed");
        return config;
    }
}

So once this little application is running, you can see the output for different mime types. In our case the default for "application/json" is "detailed" but I can see the argument in many cases for "normal" to be the default.

GET .../hello Accept application/json; level=detailed or application/json

{
    "message" : "This is indeed the message",
    "subtext" : "This is the deep and meaningful subtext",
    "summary" : "Some simple summary"
}

GET .../hello Accept application/json; level=normal

{
    "message" : "This is indeed the message",
    "summary" : "Some simple summary"
}

GET .../hello Accept application/json; level=summary

{
    "summary" : "Some simple summary"
}


This does work, but as you can see the annotations could be prettier, the next blog post will be an example using EntityFilteringFeature of Jersey. This feature builds on top of the MOXy functionality above while allowing a custom annotation scheme. This to my mind is a lot easier to work with.

Tuesday, April 30, 2013

Using Java WebSockets, JSR 356, and JSON mapped to POJO's

So I have been playing around with Tyrus, the reference implementation of the JSR 356 WebSocket for Java spec. Because I was looking at test tooling I was interested in running both the client and the server side in Java. So no HTML5 in this blog post I am afraid.

In this example we want to sent JSON back and forth and because I am old fashioned like that I want to be able to bind to a POJO object. I am going to use Jackson for this so my maven file looks like this:

<dependencies>
    <dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>1.0-rc3</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-client</artifactId>
        <version>1.0-rc3</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-server</artifactId>
        <version>1.0-rc3</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-container-grizzly</artifactId>
        <version>1.0-rc3</version>
    </dependency>
    

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.2.0</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.2.0</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.2.0</version>
    </dependency>

  </dependencies>

So the first things we need to do is to define an implementations of the Encode/Decoder interfaces to do this work for us. This is going to do some simple reflection to workout what the bean class is. Like with JAX-WS it is easier to put them on the same class. Note that we use the streaming version of the interface and are only handling text content. (Ignoring the ability to send binary data for the moment)

package websocket;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

public abstract class JSONCoder<T>
  implements Encoder.TextStream<T>, Decoder.TextStream<T>{


    private Class<T> _type;
    
    // When configured my read in that ObjectMapper is not thread safe
    //
    private ThreadLocal<ObjectMapper> _mapper = new ThreadLocal<ObjectMapper>() {

        @Override
        protected ObjectMapper initialValue() {
            return new ObjectMapper();
        }
    };
    

    @Override
    public void init(EndpointConfig endpointConfig) {
    
        ParameterizedType $thisClass = (ParameterizedType) this.getClass().getGenericSuperclass();
        Type $T = $thisClass.getActualTypeArguments()[0];
        if ($T instanceof Class) {
            _type = (Class<T>)$T;
        }
        else if ($T instanceof ParameterizedType) {
            _type = (Class<T>)((ParameterizedType)$T).getRawType();
        }
    }

    @Override
    public void encode(T object, Writer writer) throws EncodeException, IOException {
        _mapper.get().writeValue(writer, object);
    }

    @Override
    public T decode(Reader reader) throws DecodeException, IOException {
        return _mapper.get().readValue(reader, _type);
    }

    @Override
    public void destroy() {
        
    }

}

The bean class is really quite simple with a static subclass of the Coder that we can use later.

package websocket;

public class EchoBean {
    
    
    public static class EchoBeanCode extends
       JSONCoder<EchoBean> {
        
    }
    
    
    private String _message;
    private String _reply;


    public EchoBean() {
        
    }

    public EchoBean(String _message) {
        super();
        this._message = _message;
    }


    public void setMessage(String _message) {
        this._message = _message;
    }

    public String getMessage() {
        return _message;
    }


    public void setReply(String _reply) {
        this._reply = _reply;
    }

    public String getReply() {
        return _reply;
    }

}

So new we need to implement our server endpoint, you can go one of two way either annotating a POJO or extending Endpoint. I am going with the first for the server and the second for the client. Really all this service does is to post the message back to the client. Note the registration of the encode and decoder. The same class in this case.

package websocket;

import java.io.IOException;

import javax.websocket.EncodeException;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import static java.lang.System.out;

@ServerEndpoint(value="/echo",
                encoders = {EchoBean.EchoBeanCode.class},
                decoders = {EchoBean.EchoBeanCode.class})
public class EchoBeanService
{
    
    @OnMessage
    public void echo (EchoBean bean, Session peer) throws IOException, EncodeException {
        //
        bean.setReply("Server says " + bean.getMessage());
        out.println("Sending message to client");
        peer.getBasicRemote().sendObject(bean);
    }

    @OnOpen
    public void onOpen(final Session session, EndpointConfig endpointConfig) {
        out.println("Server connected "  + session + " " + endpointConfig);
    }
}

Lets look at a client bean, this time extending the standard Endpoint class and adding a specific listener for a message. In this case when the message is received the connection is simply closed to make our test case simple. In the real world managing this connection would obviously be more complicated.

package websocket;

import java.io.IOException;

import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.EncodeException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;

import static java.lang.System.out;

@ClientEndpoint(encoders = {EchoBean.EchoBeanCode.class},
                decoders = {EchoBean.EchoBeanCode.class})
public class EchoBeanClient 
  extends Endpoint
{
    public void onOpen(final Session session, EndpointConfig endpointConfig) {

        out.println("Client Connection open "  + session + " " + endpointConfig);
        
        // Add a listener to capture the returning event
        //
        
        session.addMessageHandler(new MessageHandler.Whole() {

            @Override
            public void onMessage(EchoBean bean) {
                out.println("Message from server : " + bean.getReply());
                
                out.println("Closing connection");
                try {
                    session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "All fine"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        
        // Once we are connected we can now safely send out initial message to the server
        //
        
        out.println("Sending message to server");
        try {
            EchoBean bean = new EchoBean("Hello");
            session.getBasicRemote().sendObject(bean);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (EncodeException e) {
            e.printStackTrace();
        }

    }
}

Now running the WebSocket standalone is really quite straightforward with Tyrus, you simple instantiate a Server and start it. Be aware this starts daemon threads so you need to make sure if this is in a main method that you do something to keep the JVM alive.

import org.glassfish.tyrus.server.Server;

Server server = new Server("localhost", 8025, "/", EchoBeanService.class);
server.start();


So the client is relatively simple; but as we are doing the declarative method we need to explicitly register the encoders and decoders when registering the client class.

import javax.websocket.ClientEndpointConfig;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.Session;

import org.glassfish.tyrus.client.ClientManager;


// Right now we have to create a client, which will send a message then close
// when it has received a reply
//

ClientManager client = ClientManager.createClient();
EchoBeanClient beanClient = new EchoBeanClient();

Session session = client.connectToServer(
        beanClient, 
        ClientEndpointConfig.Builder.create()
         .encoders(Arrays.<Class<? extends Encoder>>asList(EchoBean.EchoBeanCode.class))
         .decoders(Arrays.<Class<? extends Decoder>>asList(EchoBean.EchoBeanCode.class))
         .build(),
        URI.create("ws://localhost:8025/echo"));
        
        
// Wait until things are closed down
        
while (session.isOpen()) {
    out.println("Waiting");
    TimeUnit.MILLISECONDS.sleep(10);
}

Now the output of this looks like the following:

Server connected SessionImpl{uri=/echo, id='e7739cc8-1ce5-4c26-ad5f-88a24c688799', endpoint=EndpointWrapper{endpointClass=null, endpoint=org.glassfish.tyrus.core.AnnotatedEndpoint@1ce5bc9, uri='/echo', contextPath='/'}} javax.websocket.server.DefaultServerEndpointConfig@ec120d
Waiting
Client Connection open SessionImpl{uri=ws://localhost:8025/echo, id='7428be2b-6f8a-4c40-a0c4-b1c8b22e1338', endpoint=EndpointWrapper{endpointClass=null, endpoint=websocket.EchoBeanClient@404c85, uri='ws://localhost:8025/echo', contextPath='ws://localhost:8025/echo'}} javax.websocket.DefaultClientEndpointConfig@15fdf14
Sending message to server
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Waiting
Sending message to client
Message from server : Server says Hello
Closing connection
Waiting

Interestingly the first time this is run the there is a pause, I suspect this is due to Jackson setting itself up but I haven't had time to profile. I did find that this long delay on occurred on the first post - although obviously this is going to be slower than just passing plain text messages in general. Whether the different is significant to you depends on your application.

It would be interesting to compare the performance of the plain text with a JSON stream API such as that provided by the new JSR and of course the version that binds those values to a JSON POJO. Something for another day perhaps.

Thursday, April 25, 2013

Building a simple java client for forecast.io

I like to be outdoors when life allows so weather is always an interest for me. So i was very pleases to see forecast.io becoming available with global coverage after being jealous of the US only "DarkSkys" app. It was also nice to see that there is a nice REST api for the service which I could play with.

Unfortunately there was only a textual description of service, nothing I could consume with the Java client tools I have been working with. But I though it would be an interesting experiment to try to document an existing service from scratch.

The first thing I did was to get an example of the data that the service would respond with and run it through a JSON->JSON Schema tool. I used JSONSchema.net which gave me a rough v3 JSON Schema; but it wasn't able to take into account elements that are reused. A little bit of hand editing later I ended up with three JSON Schema files, one for the forecast itself and one for a list of weather details and and one for each particular data point.

From there is was pretty simple to read the developer documentation and write up a simple WADL to describe the two resources provided by the service. Luckily Jason LaPort from the DarkSkys was on hand to fix a few mistake and generally verify my description.

Once I had this all in place I of course found a couple of bugs in my own code, so it was time to update and fix wadl.java.net and release a 1.1.5 version with a revised depdency on the jsonschema2pojo project. This is why a little bit of adhoc expert testing pay dividends.

So taking the WADL from github it is possible to generate a nice clean Java API using the wadl.java.net generator. I won't reproduce the steps to generate the client here, but this is the end result and the kind of code you can get out of it.

// Create a Jersey client and setup for POJO JSON mapping

    ClientConfig cc = new DefaultClientConfig();
    cc.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
    Client c = Client.create(cc);


    // Get current weather for location and check for any alerts 
    
    Forecast f = ApiForecastIo_Forecast
        .apikey(c, "xxxxxxx")                  // Get own key from developer.forecast.io
        .latitudeLongitude(51.32d, -1.244d)    // Some location near where I live
           .getAsForecast(
              null,                            // Not JSONP
              Units.SI,                        // Return values in SI units
              "hourly,minutely,daily,flags");  // Just return current weather and any alerts
    
    // Output a summary

    System.out.println(
        f.getCurrently().getSummary() + " " +
        f.getCurrently().getTemperature() + " C");

    // Output any alerts
  
    List<Alert> alerts = f.getAlerts();
    for (Alert altert : alerts) {
        System.out.println(
            altert.getTitle() + " " + altert.getUri());
    }

The nice thing here was just how pleasantly easy describing a service that already exists was, yes I could have written it by hand in Java; but then when I wanted to move to Jersey 2.x I would have to re-write by hand rather that just regenerating from the metadata to a new technology target. (Also did the same for a BBC REST Schedule API, that is in the same git project)

Friday, February 22, 2013

Using UriTemplate to change path parameters in a URI

I have been recently working on a client generator in the wadl.java.net project and I was solving a bug where I wanted to change path parameter on a URI. This turned out to be quite easy with existing classes that are part of the Jersey project.

First of all you need a instance of UriTemplate, depending on the very of Jersey this is in a slightly different package - you IDE's automatic import will do the work for you.

  String template = "http://example.com/name/{name}/age/{age}";
  UriTemplate uriTemplate = new UriTemplate(template);


Then you use match(...) to extract them:

  String uri = "http://example.com/name/Bob/age/47";
  Map<String, String> parameters = new HashMap<>();

  // Not this method returns false if the URI doesn't match, ignored
  // for the purposes of the this blog.
  uriTemplate.match(uri, parameters);

Then you can replace the parameters at will and rebuild the URI:

  parameters.put("name","Arnold");

  UriBuilder builder = UriBuilder.fromPath(template);
  URI output = builder.build(parameters);

Wednesday, October 19, 2011

A little script to update job descriptions on Matrix jobs

We have started to use Matrix jobs in Hudson to try and reduce the amount of clutter on the front page; but we have run into problems in that the job description plugin we used to add the LABEL name to the job id only works for the subordinate tasks. This leaves the top level job without a description.

There are of course other ways you can do this; but since I was playing with the new REST api yesterday, I am going to use this. You could of course use the old RESTy API or indeed the command line tool to perform similar feats. This week I mostly just have a hammer.

Before you use the proxy classes you need to first create a client with the right security credentials configured:

   public static Client createClient() {
       Client client = Client.create();
       client.addFilter(new HTTPBasicAuthFilter("xxxxx@oracle.com","xxxxxxx"));
       return client;
   }

The first part of the job is going to be to create proxies for both the parent project and of the child variant:

   public static void main(String[] args) {

       if (args.length!=2) {
           System.err.println("Expecting two parameters project and variant");
       }

       String project = args[0];
       String variant = project + "%2F" + args[1]; 

       System.out.printf("Will copy missing description from %s to %s\n", variant, project);

       // Create a Jersey client
       Client client = createClient();

       // Create a suitable proxy using the client we configured
       Projects hudsonProjects = projects(client);

       // Handles to the right resources
       Projects.ProjectNameBuilds projectBuilds = hudsonProjects.projectNameBuilds(project);
       Projects.ProjectNameBuilds variantBuilds = hudsonProjects.projectNameBuilds(variant);

Then we can make a quick map of the variant build number to descriptions by getting the first build object:

       // Copy the descriptions out from the variant
       Map<Integer, String> variantToDescription = new HashMap<Integer,String>();
       BuildsDTO variantBuildsDTO = variantBuilds.getAsApplicationXml(BuildsDTO.class);
       for (BuildDTO build : variantBuildsDTO.getBuild()) {
           if (build.getDescription()!=null)
           {
               variantToDescription.put(
                   build.getNumber(), 
                   build.getDescription());
           }
       }

Now it turns out that the current version of the API doesn't allow you to update build descriptions; but we can easily read them using this API and then update them using a simple form POST using the same client.


       // Update the main project descriptions
       BuildsDTO projectBuildsDTO = projectBuilds.getAsApplicationXml(BuildsDTO.class);
       for (BuildDTO build : projectBuildsDTO.getBuild()) {

           String description = build.getDescription();
           // Update description if it has not already been set
           if (description == null || description.length()==0) {

               String variantDesc = variantToDescription.get(build.getNumber());
               if (variantDesc!=null && variantDesc.length()!=0) {

                   // We need to set the description; but nothing in the REST API
                   // so we can use the url property to perform a form submit that will
                   // update the description for us.

                   MultivaluedMap map = new Form();
                   map.add("description", variantDesc);
                   map.add("Submit", "Submit");
                   WebResource buildResource = client.resource(
                       build.getUrl() + "submitDescription");
                   ClientResponse response = buildResource.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
                       .post(ClientResponse.class, map);
                   System.out.printf("Updating build %d with description %s gave response %d\n",
                                     build.getNumber(), variantDesc, response.getStatus());

               }
           }
       }

Wednesday, January 27, 2010

A little bit of REST with Oracle ADF / SDO

So I have been working on my BuildAnApp project, that I have mentioned previously, and decided to try and take the day out to build a simple RESTful interface to my ADF application module. Just a simple resource so I can create and update items from a single table.

The problem is always how to convert you objects into XML, so I decided to expose the Application Module with a Service interface. This results in an set of methods that can update the model in terms of SDO object which are easy to convert into XML.

I then created another project in JDeveloper along with a class called Root configured as a JAX-RS resource, you can find more about working with Jersey / JAX-RS web services in the help for 11R1PS1. I won't go into details here.

One of the limitations of Jersey is that it isn't yet properly integrated in to the container so nice JEE annotations such as @Resource don't work properly. I blogged about this previously but you can just create a class that looks like this as a short cut:

package org.concept.model.rest;

import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import java.lang.reflect.Type;

import javax.annotation.Resource;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.servlet.ServletConfig;


public class InjectingAdapter extends ServletContainer {
    @Override
    protected void configure(ServletConfig servletConfig, ResourceConfig rc,
                             WebApplication wa) {
        super.configure(servletConfig, rc, wa);


        rc.getSingletons().add(new InjectableProvider<Resource, Type>() {

                public ComponentScope getScope() {
                    return ComponentScope.PerRequest;
                }

                public Injectable<Object> getInjectable(ComponentContext ic,
                                                        Resource r, Type c) {

                    final String name = r.name();


                    return new Injectable<Object>() {

                        public Object getValue() {
                            
                            Object value = null;

                            try {
                                Context ctx = new InitialContext();
        

                                // Look up a data source
                                try {
                                    value = ctx.lookup(name);
                                } catch (NamingException ex) {

                                    value =
                                            ctx.lookup("java:comp/env/" + name);
                                }

                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }

                            return value;
                        }
                    };
                }
            });
    }
}

You have to modify the web.xml that JDeveloper generates to use this new class rather than the default Jersey servlet. I have also added in a ejb-ref that will pick up the service interface from the application module.

<web-app>

  ...

  <servlet>
    <servlet-name>jersey</servlet-name>
    <servlet-class>org.concept.model.rest.InjectingAdapter</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <ejb-ref>
    <ejb-ref-name>ConceptServiceInterface</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <remote>org.concept.model.am.common.serviceinterface.ConceptModuleService</remote>
    <ejb-link>org.concept.model.am.common.ConceptModuleServiceBean</ejb-link>
  </ejb-ref>
</web-app>

The next thing you have to worry about is that Jersey doesn't know how to deal with SDO objects out of the box. So we need to provide a Writer and Reader, these can be placed on the class path and Jersey will pick them up. Note that the implementation is far from production quality; but it is enough to get started.

package org.concept.model.rest;


import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLHelper;

import java.io.IOException;
import java.io.OutputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;


@Produces("application/sdo+xml")
@Provider
public class SDOMessageBodyWriter implements MessageBodyWriter<DataObject> {
    public SDOMessageBodyWriter() {
        super();
    }

    public boolean isWriteable(Class c, Type type, Annotation[] annotation,
                               MediaType mediaType) {
        return true;
    }


    public long getSize(DataObject dataObject, Class<?> class1, Type type,
                        Annotation[] annotations, MediaType mediaType) {
        return -1L;
    }

    public void writeTo(DataObject dataObject, Class<?> class1, Type type,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> multivaluedMap,
                        OutputStream outputStream)  {

        // From http://wiki.eclipse.org/EclipseLink/Examples/SDO/StaticAPI
        
        // and http://www.eclipse.org/eclipselink/api/1.1/index.html


        try {
            commonj.sdo.Type sdoType = dataObject.getType();
            XMLHelper.INSTANCE.save(dataObject, sdoType.getURI(), sdoType.getName(), outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

And the reader implementation:

package org.concept.model.rest;


import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLDocument;
import commonj.sdo.helper.XMLHelper;

import java.io.InputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;


@Consumes("application/sdo+xml")
@Provider
public class SDOMessageBodyReader implements MessageBodyReader<DataObject> {
    public boolean isReadable(Class<?> class1, Type type,
                              Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    public DataObject readFrom(Class<DataObject> class1, Type type,
                               Annotation[] annotations, MediaType mediaType,
                               MultivaluedMap<String, String> multivaluedMap,
                               InputStream inputStream) {
        
        try
        {
            XMLDocument xmldocument = XMLHelper.INSTANCE.load(inputStream);        
            DataObject dos = xmldocument.getRootObject();
            return dos;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

Now for the root resource, and we provide methods to get a list of concepts in the form of a URIList, get the data for a particular Concept, and update the values for a concepts. Delete and create are left as an exercise for the reader.

package org.concept.model.rest;


import commonj.sdo.helper.DataFactory;

import java.math.BigDecimal;

import java.net.URI;

import java.util.List;

import javax.annotation.Resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import oracle.jbo.common.service.types.FindControl;
import oracle.jbo.common.service.types.FindCriteria;

import org.concept.model.am.common.serviceinterface.ConceptModuleService;
import org.concept.model.vo.common.ConceptViewSDO;


@Path("/")
public class Root {

  @Resource(name = "ConceptServiceInterface")
  private ConceptModuleService service;
 

  @GET
  @Path("/concepts/")
  @Produces("application/xml")
  public Response getConcepts(
      @Context UriInfo info,
      @QueryParam("start") Integer start,
      @QueryParam("size") Integer size) {
    
    // If we don't have the start and end then redirect with one otherwise
    // the client could get a lot of data back
    //
    if (size==null) {
      return Response.seeOther(
        info.getRequestUriBuilder().queryParam("start", start!=null?start : 0)
                                   .queryParam("size", 10).build()).build();
    }
    
    //

    FindCriteria criteriaImpl =
      (FindCriteria)DataFactory.INSTANCE.create(FindCriteria.class);
    criteriaImpl.setFetchStart(start);
    criteriaImpl.setFetchSize(size);
    
    
    FindControl controlImpl =
      (FindControl)DataFactory.INSTANCE.create(FindControl.class);
    List list =
      service.findConceptView(criteriaImpl, controlImpl);

    // Build the uri list to return to the client
    //
    
    URI uriList[] = new URI[list.size()];
    int i = uriList.length;
    for (int j = 0; j < i; j++) {
      uriList[j] = info.getBaseUriBuilder().path("concept").path(
        list.get(j).getConceptId().toString()).build();
    }

    return Response.ok(new URIList(uriList)).build();
  }

  

  @GET
  @Path("/concepts/{concept}")
  @Produces("application/sdo+xml")
  public ConceptViewSDO getConcept(@PathParam("concept")
    BigDecimal conceptId) {
    ConceptViewSDO conceptView = service.getConceptView(conceptId);
    return conceptView;
  }


  @PUT
  @Path("/concepts/{concept}")
  @Consumes("application/sdo+xml")
  public void updateConcept(
    @PathParam("concept")
        BigDecimal conceptId,
    ConceptViewSDO concept) {

    // Check we are updating the correct concept
    //
    if (!conceptId.toBigInteger().equals(concept.getConceptId())) {
      throw new WebApplicationException(
              Response.Status.CONFLICT);
    }
    
    service.updateConceptView(concept);
  }


}

Now there is a lot more I could do for this resource, for example next and previous links to make it easier to iterate over the list. In the query method we should modify the FindCriteria to only return the ID attributes for performance reasons. We should also be providing action links for operations that we wish to perform on the concept entity. Finally since SDO allows you to easily generate a change summary it would be nice to be able to support the new HTTP PATCH method.

As I work on the project I will update this page as I add more features. There is some interesting hypermedia stuff coming in future versions of Jersey and I hope to be able to update this example to take this into account.

Friday, January 8, 2010

JAX-WS Proxy client for a WSDL that container HTTP Binding rather than SOAP Binding

I came across a curios bug where the WSDL uses HTTP Binding rather than SOAP Binding. This caused all sort of hell with the proxy wizard in JDeveloper, which I am working on, but I still ran into problems when I tried to run the client that was generated from the wsimport tool.

It turns out that the JAX-WS dynamic proxy client, the ones with the SEI, doesn't support HTTP/REST out of the box. In order to have this working you need to add both a binding feature and to set the endpoint address. So you code looks something like this:

    Port port = service.getMyPort(
        new BindingTypeFeature(JAWWSProperties.REST_BINDING));
    ((BindingProvider)port).getRequestContext(
        BindingProivider.ENDPOINT_ADDRESS_PROPERTY,
        "http://..../rest-like-endpoint");
    port.doSomethingInteresting(...);

Not something you will come across every day; but worth knowing all the same.

Friday, June 12, 2009

Roy Fielding's Untangled blog

Came up a few times at JavaOne, makes an interesting read.

Friday, June 5, 2009

Async REST

I was asked today at the end of the presentation about what we thought an Async REST service would look like. At the time I was not sure; but after thinking about it the pattern is probably quite simple.

Consider an business making an insurance claim of some kind on behalf of an employee. So the client sends a POST message to the insurance resource as it want to create a new insurance claim. In the message it contains not only a reference to the resource for the employee but also a new sub resource for the employee that represents the claim. Since we are doing async the server response with 201 and with a URI to a resource to represent the insurance claim.

Time passes...

Eventually the server processing the work will POST its response back into the particular claim resource which we specified on invocation. In my example the message contains a reference to the insurance claim; but I suspect this is not required as would be implicit in the use of the resource on the client.

Now since the response is a POST as well it creates a new resource, in this case a claim response, which is returned to the server. In my experience insurance claims often take multiple goes so there could be multiple responses for a give claim. This would appear to complete the asynchronous message exchanges.

Does appear to make sense, mind you it is 00:30 at the moment so I will have to review in the morning, and all without the use of any WS-* standard.

Friday, May 29, 2009

HATEOAS, REST in the HTTP Analyzer in JDeveloper

I have been working on adding some REST support to JDeveloper back in the labs. In particular I have been focusing on support for HATEOAS in the HTTP Analyzer. I put together a quick viewlet that demonstrates the basic features to get some early feedback.

I cannot say if or when these features will ever see the light of day as per the standard Oracle SOP so please take this into consideration when watching the viewlet.

Update: I noticed that in the demo the create method returns 303, it should proper return "201 Created" to be correct.

Thursday, May 21, 2009

New project to watch, atmosphere : cometd for the masses

A new project called atmosphere has popped up that aims to make writing and deploying cometd application really easy. You don't need to install any fancy web containers such as grizzly, just drop a war in whatever you are current using. It then appears to make use of facilities as they are available. (for example Servlet 3.0)

The basic examples look interesting; but where the power seems to come with when combined with Jersey. So you can write a service that looks like this:

 @Suspend
 @GET
 @Produces("text/html")
 public String cometGet() {
     return "\n";
 }

 @Broadcast
 @Consumes("application/x-www-form-urlencoded")
 @POST
 @Produces("text/html")
 public String cometPost(MultivaluedMap form) {
     String action = form.getFirst("action");
     String name = form.getFirst("name");

     if ("login".equals(action)) {
         return BEGIN_SCRIPT_TAG + toJsonp("System Message", name + " has joined.") + END_SCRIPT_TAG;
     } else if ("post".equals(action)) {
         return BEGIN_SCRIPT_TAG + toJsonp(name, form.getFirst("message")) + END_SCRIPT_TAG;
     } else {
         throw new WebApplicationException(422);
     }
 }

 @OnBroadcast
 public String cometPush(String s) {
     System.out.println("cometPush: " + s);
     return s;
 } 

Hopefully the full text of this example will be up on the site as promised soon.

There is a BOF-5009 late Tuesday night which I will try to attend jet lag allowing. But this really does appear to make cometd service in Java real easy... now I wonder if it deploys to AppEngine yet...

Tuesday, April 21, 2009

Some notes on why HATEOAS is good

Just a few notes on the experience of writing a RESTful API for the Sun Cloud component. A bit more commentary here on InfoQ.

Monday, April 20, 2009

Injecting Jersey Clients

I had to look this up for someone and it took me a little while so here is a link to a Paul Sandoz post about injecting Jersey API client. So this allow you to inject a client for a particular resource. Paul get bonus points for defaulting to the asynchronous form, in my opinion this should be the default for all web service clients....

Thursday, January 22, 2009

Getting Firefox to use the HTTP Analyzer when run from within JDeveloper

It is really helpful when debugging AJAX application to see the traffic between the the web browser and the client. You can do this using the HTTP Analyzer; but it can get really annoying to have to set and reset your proxy settings in the browser.

It turns out that if you are using firefox you can get around this by using named profiles. The following assumes Linux, but windows people can just clag on .exe to the commands. One thing you have to understand is that by default the firefox command will just open a window on your currently open instance of firefox so you need to use "-no-remote" to create a separately configured instance. So from the command line run:

firefox -no-remote -CreateProfile Debugging

Now just quickly start a firefox with this profile so you can configure the proxy settings to use the analyzer in JDeveloper. (localhost, 8099 and no host exclusions)

firefox -no-remote -P Debugging

So you need to configure JDeveloper to start this special firefox every time, simply go to Tools->Preferences and paste in this command line. It is likely that JDeveloper will complain it cannot verify it and underline it in red; but it does seem to work okay:

So all you have to do is to start the HTTP Analyzer and run HTML/JSP/JSF page from within JDeveloper and a new instance of firefox using the Debugger profile will be started. The downside is that this is less memory efficient as for each run you will be creating a new copy of firefox; but it seems worth it to simplify development.

Annoyingly this trick can't use used directly to configure the java script debugger, due to a bug in the handling of command lines. A work around might be to create a wrapper script for firefox that passed on the above parameters along with any passed values. Don't have time to try that today though.

Monday, January 5, 2009

Some nice RESTful articles from InfoQ and Others

Just getting back into the swing of things and ended up with a nice long tab group of links. Figured I would write them down as they represent quite a nice introduction of the key concepts.

Thursday, October 2, 2008

Running JAX-RS/Jersey/JSR311 on weblogic

Please check the bottom of this post for the latest information.

Now that JSR311 has been approved I thought it was time to have a quick go at running a simple hello world service using JDeveloper / Weblogic. These instructions should give you enough of a hint though if you are using Ant/Eclipse or some other tool to build your services. Turns out for weblogic there a few issues to be work around first before you will get a running service.

First of all you need to download some jar files, not quite sure why but the jersey folks seem to go about of there way to make things harder for you if you are not using Maven. You need to follow the "Download" link from the main jersey page. (I can't provide a hyperlink as this page moves with each release, this instructions are based on 0.9). The minimum list of jars you need to run simple services is:

  • jersey-bundle-*.jar
  • jsr311-api.jar
  • asm-*.jar

In JDeveloper create a new library against a project with these three jar files in them. Make sure you mark the library as "Deploy Always". For the moment you wont find these jars in a j2ee server.

In the project you want to create you RESTful services in you need to create a web.xml that contains a reference to the root JAX-RS servlet. This wont be required when we get JEE6; but it doesn't take too much effort to put the extra entries in place. Once you have finished your web.xml will look like:

<?xml version = '1.0' encoding = 'ISO-8859-1'?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
    <description>Empty web.xml file for Web Application</description>
    <session-config>
        <session-timeout>35</session-timeout>
    </session-config>
    <mime-mapping>
        <extension>html</extension>
        <mime-type>text/html</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>txt</extension>
        <mime-type>text/plain</mime-type>
    </mime-mapping>
    <servlet>
        <display-name>JAX-RS Servlet</display-name>
        <servlet-name>jersey</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
            <param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>project1</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>jersey</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

You need to edit the value for "com.sun.jersey.config.property.packages" to contain a ';' separated list of package names to search. The default configuration class that scans the entire classpath doesn't work on weblogic. Hopefully they will fix this before 1.0 comes out.

So let quickly create a hello world service so we can test things out:

1  package project1;
 2  
 3  import javax.ws.rs.Path;
 4  import javax.ws.rs.GET;
 5  import javax.ws.rs.Produces;
 6  
 7  
 8  @Path("/helloworld")
 9  public class HelloRS {
10   
11      // The Java method will process HTTP GET requests
12      @GET
13      // The Java method will produce content identified by the MIME Media
14      // type "text/plain"
15      @Produces("text/plain")
16      public String getClichedMessage() {
17          // Return some cliched textual content
18          return "Hello World";
19      }
20  }

You have a little bit more work to do to get this working, since JDeveloper doesn't "know" about JAX-RS you have to deploy rather than just run this java file So create a WAR file deployment profile for the project and a EAR file deployment profile that contains the WAR at the application level. Finally create a weblogic-application.xml deployment descriptor for the EAR and add the following text. (In the application navigator go to the "Application Resource Section", then Descriptors->META-INF and the context menu "Create Weblogic Descriptor".

<?xml version = '1.0' encoding = 'ISO-8859-1'?>
<weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-application">
  <prefer-application-packages>
    <package-name>org.objectweb.*</package-name>
  </prefer-application-packages>
</weblogic-application>

This makes sure that the version of asm that comes with JAX-RS overrides the one that is built into weblogic. Otherwise you get a method not found error when the service deploys. You should now be able to deploy the service and test using a URL such as "http://localhost:7101/Application1-Project1-context-root/helloworld". The context root and machine/port will of course vary depending on your environment.

You can test this in a web browser, or in JDeveloper you can test using the HTTP Analyzer. View->HTTP Analyzer, Create New Message toolbar button and navigate to the "HTTP Content" tab. Put your URL in at the top and change the method to "GET" rather than the default "POST". Send the message as you should get back "Hello World" in plain text.

Right no time left to play today, back to fixing bugs.

Update 9 Dec '08:

There is an alternative to creating the weblogic-application.xml file that I discovered recently. I wasn't happy with this solution as it affected the entire EAR file. Instead you can create a "weblogic.xml" under WEB-INF that looks like this:

<?xml version = '1.0' encoding = 'ISO-8859-1'?>
<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app">
<container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
  </container-descriptor>
</weblogic-web-app>

This has the advantage of only affecting the .war file you are deploying the restful services in. Also I have found out that this problem with the clashing jar files appears to have gone away in the "next" big release of weblogic, but I can't promise you when or even if you will see this fix.

Update 13 Feb 13 '09: With the release of Jersery 1.0.2 you can now download with the basic dependencies, the json libraries and javadoc from the download page. This makes it much easier for non maven users to pick up and use it. Make sure you include both the -server and -core libraries as the -bundle version is not present in the zip.

  • jersey-core-*.jar
  • jersey-server-*.jar
  • jsr311-api-1.0.jar
  • asm-*.jar

Update 24 July '09: You can now download all the core dependencies in one big lump in the jersey-archive take a look at the download page for details.

Update 24 July '09: For weblogic server 10.3.1 aka the R1 release you no longer have to use the "prefer-web-inf-classes" modifier as the namespace in the internal copy of asm has been changed.

Update 12th August '09: (We think this only work for the yet unreleased R11PS1/10.3.2 version) For weblogic server 10.x you can can fix the annotation scanner issue so you no longer have to specify the package location. The failure is caused because ServletContext.getRealPath(...) returns null when you deploy an application as a jar file. You can work around this with the fix for weblogic issue CR299135.

In the simplest case this means your web.xml simplifies to:

<?xml version = '1.0' encoding = 'ISO-8859-1'?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
    <servlet>
        <display-name>JAX-RS Servlet</display-name>
        <servlet-name>jersey</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>jersey</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

You could create a weblogic.xml under META-INF, see similar instructions before as to how to create weblogic-webservices.xml:

<?xml version = '1.0' encoding = 'ISO-8859-1'?>
<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app http://www.bea.com/ns/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd"
                  xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app">
  <container-descriptor>
    <show-archived-real-path-enabled>true</show-archived-real-path-enabled>
  </container-descriptor>
</weblogic-web-app>

Or make the changes to config.xml mentioned in the CR' above.

Update 1st December '09: Jersey issue 32 has been resolved, if you pull the latest 1.5ea build you should be able to deploy with no workaround. JDeveloper R11 PS1 will unfortunately still create the weblogic.xml automatically; but we can fix this in a future release once 1.5 is production.

Update 11th of March '10: Some final issues, resolved in Jersey 1.1.5.1 specifically for weblogic. Resolved the null pointer in web scanner and some problems with package level filtering that has been reported here.

Update 25 of May '11: Some problems running Jersey with the Oracle XDK in the latest versions of JDeveloper.