Showing posts with label jax-rs. Show all posts
Showing posts with label jax-rs. Show all posts

Tuesday, February 10, 2015

Per client cookie handling with Jersey

A lot of REST services will use cookies as part of the authentication / authorisation scheme. This is a problem because by default the old Jersey client will use the singleton CookieHandler.getDefault which is most cases will be null and if not null will not likely work in a multithreaded server environment. (This is because in the background the default Jersey client will use URL.openConnection)

Now you can work around this by using the Apache HTTP Client adapter for Jersey; but this is not always available. So if you want to use the Jersey client with cookies in a server environment you need to do a little bit of reflection to ensure you use your own private cookie jar.

final CookieHandler ch = new CookieManager();
    
    Client client = new Client(new URLConnectionClientHandler(
       new HttpURLConnectionFactory() {

        @Override
        public HttpURLConnection getHttpURLConnection(URL uRL) throws IOException {
            HttpURLConnection connect = (HttpURLConnection) uRL.openConnection();
        
            try {
                
                Object toModify = connect;
                
                if (!(toModify instanceof sun.net.www.protocol.http.HttpURLConnection)) {
        
                    Field delegateField = connect.getClass().getDeclaredField("delegate");
                    delegateField.setAccessible(true);
                    toModify = MethodHandles.lookup().unreflectGetter(delegateField)
                        .bindTo(toModify).invoke();
                    
                }
        
                Field cookieField = sun.net.www.protocol.http.HttpURLConnection.class.getDeclaredField("cookieHandler");
                cookieField.setAccessible(true);
                MethodHandle mh = MethodHandles.lookup().unreflectSetter(cookieField);
                mh.bindTo(toModify).invoke(ch);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        
            return connect;

        }
    }));
    

This will only work if your environment is using the internal implementation of sun.net.www.protocol.http.HttpURLConnection that comes with the JDK. This appears to be the case for modern versions of WLS.

For JAX-RS 2.0 you can do a similar change using Jersey 2.x specific ClientConfig class and HttpUrlConnectorProvider.

final CookieHandler ch = new CookieManager();


    Client client =
        ClientBuilder.newClient(new ClientConfig().connectorProvider(new HttpUrlConnectorProvider().connectionFactory(new HttpUrlConnectorProvider.ConnectionFactory() {
            @Override
            public HttpURLConnection getConnection(URL uRL) throws IOException {
                HttpURLConnection connect = (HttpURLConnection) uRL.openConnection();
            
                try {
                    
                    Object toModify = connect;
                    
                    if (!(toModify instanceof sun.net.www.protocol.http.HttpURLConnection)) {
            
                        Field delegateField = connect.getClass().getDeclaredField("delegate");
                        delegateField.setAccessible(true);
                        toModify = MethodHandles.lookup().unreflectGetter(delegateField)
                            .bindTo(toModify).invoke();
                        
                    }
            
                    Field cookieField = sun.net.www.protocol.http.HttpURLConnection.class.getDeclaredField("cookieHandler");
                    cookieField.setAccessible(true);
                    MethodHandle mh = MethodHandles.lookup().unreflectSetter(cookieField);
                    mh.bindTo(toModify).invoke(ch);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            
                return connect;

            }
        })));



Update 11th Feb 2015: It seems in some cases, in particular using https, I have seen the HttpURLConnection wrapped in another class, to work around this just use reflection to access the value of the delegate field. I have updated the code examples to reflect this issue.

Wednesday, May 21, 2014

Reading and writing JAX-RS Link objects

JAX-RS contains a rather nice handy representation of the a Link that can be serialised with and adapter into XML and JSON, unfortunately there is a bug in the JAX-RS spec that means that the standard adapter provided is missing the setters required to deserialise the Link later on.

This isn't a problem if you are using the JAX-B RI as it appears to be more relaxed than the standard; but it will be a problem for other implementations. There is a further bug if you are using MOXy, aka EclipseLink, to produce either JSON or XML that it will fail and just call toString() because it doesn't like the type adapter being an inner class of the class that is being adapted. (Bug TBC)


Direction JAXB RI + JAX-RS Adapter MOXy + JAX-RS Adapter MOXy + Outer Adapter MOXy + Outer Adapter + Setters MOXy + Inner Adapter (Different class)
Marshalling Yes No Yes Yes Yes
Unmarshalling Yes No No Yes No


The workaround is to create a new adapter as a top level class in order to replace the one provided by the standard. Just quickly it would look something like this:

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.core.Link;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;

public class LinkAdapter
    extends XmlAdapter {

    public LinkAdapter() {
    }

    public Link unmarshal(LinkJaxb p1) {
        
        Link.Builder builder = Link.fromUri(p1.getUri());
        for (Map.Entry<QName, Object> entry : p1.getParams().entrySet()) {
            builder.param(entry.getKey().getLocalPart(), entry.getValue().toString());
        }
        return builder.build();
    }

    public LinkJaxb marshal(Link p1) {
        
        Map<QName, Object> params = new HashMap<>();
        for (Map.Entry<String,String> entry : p1.getParams().entrySet()) {
            params.put(new QName("", entry.getKey()), entry.getValue());
        }
        return new LinkJaxb(p1.getUri(), params);
    }
}

With a simple POJO to go with it.

import java.net.URI;

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.namespace.QName;

public class LinkJaxb  {

    private URI uri;
    private Map<QName, Object> params;


    public LinkJaxb() {
        this (null, null);
    }

    public LinkJaxb(URI uri) {
        this(uri, null);
    }

    public LinkJaxb(URI uri, Map<QName, Object> map) {
        
        this.uri = uri;
        this.params = map!=null ? map : new HashMap<QName, Object>();
        
    }



    @XmlAttribute(name = "href")
    public URI getUri() { 
        return uri;
    }

    @XmlAnyAttribute
    public Map<QName, Object> getParams() { 
        return params;
    }

    public void setUri(URI uri) {
        this.uri = uri;
    }

    public void setParams(Map<QName, Object> params) {
        this.params = params;
    }

}


With this you can read and write the Link objects to and from XML, and JSON with MOXy, to your hearts content.

Friday, February 7, 2014

Transparent PATCH support in JAX-RS 2.0

The PATCH method is one the the less well loved HTTP methods simple because until recently there really wasn't a standard PATCH format. This has been standardized for JSON for a while now so there are quite a few libraries that will do the heavy lifting for you. For the purposes of this blog I am going to use json-patch although it would be easy to adapt this particular implementation to the patch library of your choice.

A per normal lets get the resource and bean classes out of the way. In this example code we have a simple resource that knows how to return the original object and one that allows you to perform the PATCH method. Note that the patch method just accepts the bean object, this is because of some magic we are going to do in a little bit to pre-process the patch.

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("service")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class Service {

  @GET
  public Bean get() {
    return new Bean(true);
  }

  @PATCH
  @Consumes("application/json-patch+json")
  public Bean patch(Bean input) {
    System.out.println(input.getMessage() + "  " + input.getTitle());
    return input;
  }

}


import java.util.ArrayList;
import java.util.List;

public class Bean {

  private String title = "title";
  private String message = "message";
  private List<String> list = new ArrayList<String>();

  public Bean() {
    this(false);
  }

  public Bean(boolean init) {
    if (init) {
      title = "title";
      message = "message";
      list.add("one");
      list.add("two");
    }
  }


  public void setList(List list) {
    this.list = list;
  }

  public List getList() {
    return list;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getTitle() {
    return title;
  }

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

  public String getMessage() {
    return message;
  }

}

So the @PATCH annotation is something we have to create for this example, luckily JAX-RS contains a extension meta-annotation for this purpose. We are also going to use @NameBinding as this example is using JAX-RS 2.0 so we can connect up our filter in a moment.

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.ws.rs.HttpMethod;
import javax.ws.rs.NameBinding;


@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
@Documented
@NameBinding
public @interface PATCH {
}

So here is the implementation of the ReaderInterceptor that will process the incoming stream and replace it with the patched version. Note that the class is annotated with @PATCH also in order to make the @NamedBinding magic work and also that there is a lot of error handling that is missing as this is a simple POC.

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.github.fge.jsonpatch.JsonPatch;
import com.github.fge.jsonpatch.JsonPatchException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

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

import javax.ws.rs.GET;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;

import org.glassfish.jersey.message.MessageBodyWorkers;

@Provider
@PATCH
public class PatchReader implements ReaderInterceptor {
  private UriInfo info;
  private MessageBodyWorkers workers;

  @Context
  public void setInfo(UriInfo info) {
    this.info = info;
  }

  @Context
  public void setWorkers(MessageBodyWorkers workers) {
    this.workers = workers;
  }

  @Override
  public Object aroundReadFrom(
    ReaderInterceptorContext readerInterceptorContext) 
    throws IOException,
           WebApplicationException {

    // Get the resource we are being called on, 
    // and find the GET method
    Object resource = info.getMatchedResources().get(0);

    Method found = null;
    for (Method next : resource.getClass().getMethods()) {
      if (next.getAnnotation(GET.class) != null) {
        found = next;
        break;
      }
    }


    if (found != null) {

      // Invoke the get method to get the state we are trying to patch
      //
      Object bean;
      try {
        bean = found.invoke(resource);
      } catch (Exception e) {
        throw new WebApplicationException(e);
      }
      
      // Convert this object to a an aray of bytes 
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      MessageBodyWriter<? super Object> bodyWriter =
        workers.getMessageBodyWriter(Object.class, bean.getClass(), 
          new Annotation[0], MediaType.APPLICATION_JSON_TYPE);

      bodyWriter.writeTo(bean, bean.getClass(), bean.getClass(), 
          new Annotation[0], MediaType.APPLICATION_JSON_TYPE,
          new MultivaluedHashMap<String, Object>(), baos);


      // Use the Jackson 2.x classes to convert both the incoming patch  
      // and the current state of the object into a JsonNode / JsonPatch
      ObjectMapper mapper = new ObjectMapper();
      JsonNode serverState = mapper.readValue(baos.toByteArray(), 
        JsonNode.class);
      JsonNode patchAsNode = mapper.readValue(
         readerInterceptorContext.getInputStream(), 
        JsonNode.class);
      JsonPatch patch = JsonPatch.fromJson(patchAsNode);

      try {
        // Apply the patch
        JsonNode result = patch.apply(serverState);

        // Stream the result & modify the stream on the readerInterceptor
        ByteArrayOutputStream resultAsByteArray = 
          new ByteArrayOutputStream();
        mapper.writeValue(resultAsByteArray, result);
        readerInterceptorContext.setInputStream(
          new ByteArrayInputStream(
            resultAsByteArray.toByteArray()));

        // Pass control back to the Jersey code
        return readerInterceptorContext.proceed();


      } catch (JsonPatchException e) {
        throw new WebApplicationException(
          Response.status(500).type("text/plain").entity(e.getMessage()).build());
      }

    } else {
      throw new IllegalArgumentException("No matching GET method on resource");
    }


  }
}

So once you have this deployed you can start playing with the data, so the original message is:

{
  "list" : [
    "one",
    "two"
  ],
  "message" : "message",
  "title" : "title"
}

So if you apply the following patch, the result returned is:

[
  {
    "op" : "replace",
    "path" : "/message",
    "value" : "otherMessage"
  },
  {
    "op" : "add",
    "path" : "/list/-",
    "value" : "three"
  }
]


{
  "list" : [
    "one",
    "two",
    "three"
  ],
  "message" : "otherMessage",
  "title" : "title"
}

This example shows it is relatively trivial to add PATCH support to your classes by following a simple coding pattern and using a simple Annotation. In this way PATCH support becomes trivial as the implementation can just delegate to your existing PUT method.

Update: Mirsolav Fuksa from the Jersey team reminded me that in order for this implementation to comply with the PATCH RFC it should provide the Accept-Patch header when the client performs an OPTIONS request. You can do this with a simple CotnainerResponseFilter:

import java.io.IOException;

import java.util.Collections;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;

@Provider
public class OptionsAcceptHeader implements ContainerResponseFilter {

  @Override
  public void filter(ContainerRequestContext requestContext,
                     ContainerResponseContext responseContext) throws IOException {

    if ("OPTIONS".equals(requestContext.getMethod())) {
      if (responseContext.getHeaderString("Accept-Patch")==null) {
        responseContext.getHeaders().put(
          "Accept-Patch", Collections.<Object>singletonList("application/json-patch+json"));  
      }
    }
  }
}

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.

Thursday, October 4, 2012

wadl2java 1.1.3 released

So after a little bit of a pause we now have a new release of the wadl2java client generation tool. This is a wide range of improvements in this release; but the main feature is the new support for generating JAX-RS 2.0 client code.

This generated code is nearly identical to the Jersey 1.x interface so most simple code should compile straight after a swap without too many problems. If the client is making use of ClientResponse these references will have to be replaced with Response; but otherwise the interface will be consistent. I would welcome suggestions as to how I could improve what is generated.

So here is a list of items of changes in the release:
  • WADL-25, a patch to allow the passing in of argument to xjc, as provided by Brian Chapman thanks for that..
  • There is a problem in when there was not response content type, this type of method now returns the relevant Response object.
  • Classes that are @XmlType were being wrapped incorrectly with JAXBElement and the information was being taken from the return class.
  • The Proxy objects are now immutable, modifying any property created a new instance.
  • There is a now a programmatic interface to override the base URI at each level. (Suggested by Luigi Tagliamonte)
  • Fix for Oracle Bug 13804542, generation would fail if inner and outer class names match
  • JAX-RS 2.0 generation support available in all tool modes
  • Fix for Oracle Bug 14534583, where a fault element was being incorrectly upgraded from a '2006 WADL. This was causing problems with the examples SOAPUI were providing.
  • Fix for Oracle Bug 1462282, where the generated exception classes were not actually used in the generated code. All exceptions now extend WebAppilcationException to make migration from 1.x to 2.x easier.
  • Improved method generation, removed "application" from media types, removed duplication when method takes and returns the same type (putApplicationXmlAsApplicationXml -> putXml), moved "As" to correct location when we have a type, removed excessive method combinations where we have symmetrical media types. (eg xml,json->xml,json)
  • Initial work looking at how to support JSON-Schema type generation, non functional
  • Adding functional testing for generated clients as was previously only examining the generated classes. Now they are run for both 1.x and 2.x type clients.
 Thanks again as usual help from Pavel Bucek in getting this software out the door.


Friday, November 25, 2011

x!, a very simple xml library

A couple of weeks ago, having gotten annoyed about the amount of boilerplate you need in Java in order to do some very simple XML actions I decided to put something in place that would get me close to the fluency of the Groovy and JScript XML extensions with a very small interface. Indeed the original proposal can be contained in a tweet and the only methods I have added since are parent() and prefix() which doesn't quite fit in the standard 140 characters.

So there is basically only one class to worry about X, this allows you to read and write values, select children and perform the normal input and output operations. Everything throws unchecked exceptions, and where possible Attr and Elements are treated the same so for example set(val) and get() will work for both in a consistent manner.

You can find simpler examples in the unit tests in the github project, but the most interesting use cases for me are when used with JAX-RS to access and program REST resources that accept XML. XPath makes Duck Typing easy and if you are careful your code can ignore minor schema changes, this is in stark contrast to static JAX-B binding for example.

Take this example, it looks at a remote resource and sets it as being offline using the Jersey client. In the simplest case you need to explicitly tell Jersey about the X message body readers and writers; but other than that the code is quite straight forward.

   ClientConfig clientConfig = new DefaultClientConfig(
      XMessageBodyWriter.class,XMessageBodyReader.class);    
   Client client = Client.create(clientConfig);
   client.addFilter(new HTTPBasicAuthFilter(...));
        
   WebResource resource = client.resource("http://hudson...");

   resource.put(
      resource.get(X.class)
         .set("//n:offline", "true"));
        
   System.out.println(resource.get(X.class).get("//n:offline"));

Note in the last line we use the pre-defined namespace prefix n which is always populated by the first node in the tree. You can also perform the select, or selectList for a list, explicitly to get hold of the value:

   resource.put(
      resource.get(X.class)
         .select("//n:offline").set("true"));
        
   System.out.println(resource.get(X.class)
     .select("//n:offline").get());

You can also use x! in JAX-RS resources, here is a very simple hello world example using the same message body readers and writers as before. Since they are marked as @Provider your container should be able to pick them up for you.

@Path("/hello")
public class HelloMessage {
    @POST
    @Produces("text/xml")
    @Consumes("text/xml")
    public X hello(X input) {

        String name = input.select("//name").get();

        X response = X.in("http://www.example.com", "message");
        response
            .children().create("name")
                .set(name).parent()  // Think of .parent() like a CR
            .children().create("message")
                .set("Hello " + name)
                .set("@lang", "en");

        return response;
    }
}

I am not entirely sure about the flow to create new objects, feedback is always appreciated of course. The use of @ to set attributes is quick as internally this doesn't result in a XPath query. Currently you can only work on direct children when creating new attributes, for more complex paths you need to select the direct parent to create new attributes. You can still set values on attributes that do exist with complex xpath expressions though.

    // @attr exists
    x.select("....@attr").set("value");
    or 
    s.set("...@attr", "value");

Here is a simple request and response from this service just for comparison you can get the code to reflect the xml quite closely.

// Example input message
<message>
   <name>Bob></name>
</message>

// Example response

<message xmlns="http://www.example.com">
   <name>Bob></name>
   <message lang="en">Hello Bob></message>
</message>

This really was a thought experiment that got out of control; but I would welcome feedback, suggestions, and of course as it is on GitHub patches.

One interesting direction is replacing the current DOM implementation with a streaming version which might be possible for certain read and write options depending on the ordering of the data.

Monday, September 12, 2011

Replacment for Endpoint.publish when using Jersey

Sometimes when working with JAX-WS services it is nice to be able to test the service without having to deploy to a heavyweight server. You can do this using the Endpoint.publish method and just pass in an instance of your server class.

There is something similar in Jersey, but it isn't part of the JAX-RS specification. So you can publish a single resource as follows:

package client;


import com.sun.jersey.api.container.httpserver.HttpServerFactory;
import com.sun.jersey.api.core.ClassNamesResourceConfig;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;


public class Test {
    
    public static void main(String[] args) throws IOException {

        HttpServer create = HttpServerFactory.create(
            "http://localhost:9001/",new ClassNamesResourceConfig(Hello.class));
        create.start();
    }
}

Update: 12th December 2013: The code for this has slightly changed in Jersey 2.0, the following will publish a resource using the HttpServer that comes with the JDK (you will need to include a dep on the jersey-container-jdk-http component):


import com.sun.net.httpserver.HttpServer;

import java.net.URI;

import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;


public class Test {
    public static void main(String[] args) {
        
        HttpServer d = JdkHttpServerFactory.createHttpServer(
            URI.create("http://localhost:9001"), 
            new ResourceConfig().register(new Hello()));  
   }

Wednesday, September 7, 2011

Improvements in the WADL client generator

I have recently been working on bringing the useful WADL client generator as originally penned by Marc Hadley more in line with currently technology. I have updated it so that it now uses the Jersey client and also I have made a few modifications to make it easier to use. There is plenty of documentation on the WADL site on how to run the tool in various configurations, but I thought it worth while just to show a simple example of what you might expect.

You can download a snapshot of version 1.1 from here, and hopefully soon a final version of 1.1 will be avaliable here if I have guessed the URL correctly. If you pick the version after the 6th of September it should contain all the bits your need.

So as an example we have a really simple hello world service that has a path param to select the correct languauge, the WADL and the Schema generated in Jersey 1.9 are as follows:


<!-- application.wadl -->

<?xml version = '1.0' encoding = 'UTF-8' standalone = 'yes'?>
<application xmlns="http://wadl.dev.java.net/2009/02">
    <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 1.9 09/02/2011 11:17 AM"/>
    <grammars>
        <include href="application.wadl/xsd0.xsd">
            <doc title="Generated" xml:lang="en"/>
        </include>
    </grammars>
    <resources base="http://localhost:7101/cr/jersey/">
        <resource path="webservice">
            <resource path="{lang}">
                <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="lang" style="template" type="xs:string"/>
                <resource path="greeting">
                    <method id="hello" name="GET">
                        <response>
                            <representation xmlns:ns2="http://example.com/hello" element="ns2:bean" mediaType="*/*"/>
                        </response>
                    </method>
                </resource>
            </resource>
        </resource>
    </resources>
</application>

<!-- application.wadl/xsd0.xsd -->

<?xml version = '1.0' standalone = 'yes'?>
<xs:schema version="1.0" targetNamespace="http://example.com/hello" xmlns:tns="http://example.com/hello" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="bean" type="tns:bean"/>

  <xs:complexType name="bean">
    <xs:sequence>
      <xs:element name="message" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Running this through the wadl tool you end up with a java class that look like this along with the relevant JAX-B classes for the schema type Bean.

package client;

import java.util.HashMap;

import javax.annotation.Generated;

import javax.ws.rs.core.UriBuilder;

import com.example.hello.Bean;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;


/**
 *
 */
@Generated(value = { "wadl|http://localhost:7101/cr/jersey/application.wadl" },
           comments = "wadl2java, http://wadl.java.net", date = "2011-09-07T11:53:39.206+01:00")
public class Localhost_CrJersey {


    public static Localhost_CrJersey.Webservice webservice(Client client) {
        return new Localhost_CrJersey.Webservice(client);
    }

    public static Localhost_CrJersey.Webservice webservice() {
        return webservice(Client.create());
    }

    public static class Webservice {

        private Client _client;
        private UriBuilder _uriBuilder;
        private HashMap<String, Object> _templateAndMatrixParameterValues;

        /**
         * Create new instance using existing Client instance
         *
         */
        public Webservice(Client client) {
            _client = client;
            _uriBuilder = UriBuilder.fromPath("http://localhost:7101/cr/jersey/");
            _uriBuilder = _uriBuilder.path("webservice");
            _templateAndMatrixParameterValues = new HashMap<String, Object>();
        }

        /**
         * Create new instance
         *
         */
        public Webservice() {
            this(Client.create());
        }

        public Localhost_CrJersey.Webservice.Lang lang(String lang) {
            return new Localhost_CrJersey.Webservice.Lang(_client, lang);
        }

        public static class Lang {

            private Client _client;
            private UriBuilder _uriBuilder;
            private HashMap<String, Object> _templateAndMatrixParameterValues;

            /**
             * Create new instance using existing Client instance
             *
             */
            public Lang(Client client, String lang) {
                _client = client;
                _uriBuilder = UriBuilder.fromPath("http://localhost:7101/cr/jersey/");
                _uriBuilder = _uriBuilder.path("webservice");
                _uriBuilder = _uriBuilder.path("{lang}");
                _templateAndMatrixParameterValues = new HashMap<String, Object>();
                _templateAndMatrixParameterValues.put("lang", lang);
            }

            /**
             * Create new instance
             *
             */
            public Lang(String lang) {
                this(Client.create(), lang);
            }

            /**
             * Get lang
             *
             */
            public String getLang() {
                return ((String)_templateAndMatrixParameterValues.get("lang"));
            }

            /**
             * Set lang
             *
             */
            public Localhost_CrJersey.Webservice.Lang setLang(String lang) {
                _templateAndMatrixParameterValues.put("lang", lang);
                return this;
            }

            public Localhost_CrJersey.Webservice.Lang.Greeting greeting() {
                return new Localhost_CrJersey.Webservice.Lang.Greeting(_client,
                                                                       ((String)_templateAndMatrixParameterValues.get("lang")));
            }

            public static class Greeting {

                private Client _client;
                private UriBuilder _uriBuilder;
                private HashMap<String, Object> _templateAndMatrixParameterValues;

                /**
                 * Create new instance using existing Client instance
                 *
                 */
                public Greeting(Client client, String lang) {
                    _client = client;
                    _uriBuilder = UriBuilder.fromPath("http://localhost:7101/cr/jersey/");
                    _uriBuilder = _uriBuilder.path("webservice");
                    _uriBuilder = _uriBuilder.path("{lang}");
                    _uriBuilder = _uriBuilder.path("greeting");
                    _templateAndMatrixParameterValues = new HashMap<String, Object>();
                    _templateAndMatrixParameterValues.put("lang", lang);
                }

                /**
                 * Create new instance
                 *
                 */
                public Greeting(String lang) {
                    this(Client.create(), lang);
                }

                /**
                 * Get lang
                 *
                 */
                public String getLang() {
                    return ((String)_templateAndMatrixParameterValues.get("lang"));
                }

                /**
                 * Set lang
                 *
                 */
                public Localhost_CrJersey.Webservice.Lang.Greeting setLang(String lang) {
                    _templateAndMatrixParameterValues.put("lang", lang);
                    return this;
                }

                public Bean getAsBean() {
                    UriBuilder localUriBuilder = _uriBuilder.clone();
                    WebResource resource =
                        _client.resource(localUriBuilder.buildFromMap(_templateAndMatrixParameterValues));
                    WebResource.Builder resourceBuilder = resource.getRequestBuilder();
                    resourceBuilder = resourceBuilder.accept("*/*");
                    return resourceBuilder.method("GET", Bean.class);
                }

                public <T> T getAs(GenericType<T> returnType) {
                    UriBuilder localUriBuilder = _uriBuilder.clone();
                    WebResource resource =
                        _client.resource(localUriBuilder.buildFromMap(_templateAndMatrixParameterValues));
                    WebResource.Builder resourceBuilder = resource.getRequestBuilder();
                    resourceBuilder = resourceBuilder.accept("*/*");
                    return resourceBuilder.method("GET", returnType);
                }

                public <T> T getAs(Class<T> returnType) {
                    UriBuilder localUriBuilder = _uriBuilder.clone();
                    WebResource resource =
                        _client.resource(localUriBuilder.buildFromMap(_templateAndMatrixParameterValues));
                    WebResource.Builder resourceBuilder = resource.getRequestBuilder();
                    resourceBuilder = resourceBuilder.accept("*/*");
                    return resourceBuilder.method("GET", returnType);
                }

            }

        }

    }

}

This looks little bit wordy, but things become a little bit simpler if you just look at the interface that has been generated. For example you can now access each part of the path directly using the accessor methods. In this example we are asking for just the english version of the greeting:

public class Test
{
   public static void main(String[] args) {
 
      Bean bean = Localhost_CrJersey.webservice().lang("en").greeting().getAsBean();
      System.out.println(bean.getMessage());

   }
}

Finally for each method that returns a JAX-B element there are also two that take Class.class or GenericType as parameters. This allows the client to access the ClientResponse object, or any other mapping in a way that just wasn't possible in the old API:

public class Test
{
   public static void main(String[] args) {
 
      ClientResponse response = Localhost_CrJersey.webservice().lang("en").greeting().getAs(ClientResponse.class);
      System.out.println(response.getStatus());

   }
}

Now this client currently has limitations in that it doesn't know about HATEOAS and you can't control the URL with deployment descriptors; but it is a start that can hopefully evolve into a more generally useful tool. Also I have played with the constructors for Wadl2Java a little bit to make easier to integrate into IDE tooling, it is the work of a couple of afternoons to wrap this up in a wizard and integrate this into your IDE of choice.

Wednesday, July 13, 2011

Auttomatic XML Schema generation for Jersey WADLs

I have been doing a little bit of work recently on Jersey implementation and one of the precursors to this has been to try to get the WADL that Jersey will generation by default to contain just a little bit more information with regards to the data being transferred. This makes it possible to help the user when generating client code and running testing tools against resources.

To this end I have put together a WADL generator decorator that examines all the JAX-B classes used by the -RS application and generates a bunch of XML Schema files. This is now in 1.9-SNAPSHOT which you can download from the Jersey web site in the normal way. (If you want to use the JResponse part of this you will need a build after the 13th of July)

This feature is not enabled by default in 1.9; but hopefully with some good feedback and a small amount of caching I might convince the Jersey bods to make this the default. For the moment you need to create and register a WsdlGeneratorConfig class to get this to work. So your class might look like this:

package examples;

import com.sun.jersey.api.wadl.config.WadlGeneratorConfig;
import com.sun.jersey.api.wadl.config.WadlGeneratorDescription;
import com.sun.jersey.server.wadl.generators.WadlGeneratorJAXBGrammarGenerator;

import java.util.List;

public class SchemaGenConfig extends WadlGeneratorConfig {

    @Override
    public List<WadlGeneratorDescription> configure() {
        return generator( 
                WadlGeneratorJAXBGrammarGenerator.class   ).descriptions();
    }
}

You then need to make this part of the initialization of the Jersey servlet, so your web.xml might looks like this:

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" 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">
  <servlet>
    <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.WadlGeneratorConfig</param-name>
      <param-value>examples.SchemaGenConfig</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey</servlet-name>
    <url-pattern>/jersey/*</url-pattern>
  </servlet-mapping>
</web-app>

For the purposes of this blog I am just going to show the three basic references to entities that the code supports. So at the moment if will obviously process classes that are directly referenced and either a return value or as the entity parameter on a method. The code also supports the Jersey specific class JResponse, which is a subclass of Response that can have a generic parameter. (Hopefully this oversight will be fixed in JAX-RS 2.0)

package examples;

import com.sun.jersey.api.JReponse;

import examples.types.IndirectReturn;
import examples.types.SimpleParam;
import examples.types.SimpleReturn;

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

@Path("/root")
public class RootResource {
    
    @GET
    @Produces("application/simple+xml")
    public SimpleReturn get() {
        return new SimpleReturn();
    }
    
    @GET
    @Produces("application/indrect+xml")
    public JResponse<IndirectReturn> getIndirect() {
        return JResponse.ok(new IndirectReturn() )
            .type( "application/indrect+xml" ).build();
    }
    
    @PUT
    @Consumes("application/simple+xml")
    public void put(SimpleParam param) {
        
    }
        
}

The type classes are all pretty trivial so I won't show them here. The only important factor is that they have the @XmlRootElement annotation on them. Although not shown here you can also use the JAX-B annotation @XmlSeeAlso on the resource classes to reference other classes that are not directly or indirectly referenced from the resource files. The most common use case for this is when you have a subtype of a class.

So enough of the java code, lets see what the WADL that is generated looks like:

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://wadl.dev.java.net/2009/02">
    <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 1.9-SNAPSHOT 07/13/2011 11:13 AM"/>
    <grammars>
        <include href="application.wadl/xsd0.xsd">
            <doc xml:lang="en" title="Generated"/>
        </include>
    </grammars>
    <resources base="http://localhost:7101/JerseySchemaGen-Examples-context-root/jersey/">
        <resource path="/root">
            <method name="GET" id="get">
                <response>
                    <representation xmlns:ns2="urn:example"
                        mediaType="application/simple+xml" element="ns2:simpleReturn"/>
                </response>
            </method>
            <method name="PUT" id="put">
                <request>
                    <representation xmlns:ns2="urn:example"
                        mediaType="application/simple+xml" element="ns2:simpleParam"/>
                </request>
            </method>
            <method name="GET" id="getIndirect">
                <response>
                    <representation xmlns:ns2="urn:example"
                        mediaType="application/indrect+xml" element="ns2:indirectReturn"/>
                </response>
            </method>
        </resource>
    </resources>
</application>

In this example there is only one schema in the grammar section; but the code supports multiple schemas being generated with references between them. Let look at the schema for this example, note I did say the classes were trivial!

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema version="1.0" targetNamespace="urn:example"
    xmlns:tns="urn:example" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="indirectReturn" type="tns:indirectReturn"/>
    <xs:element name="simpleParam" type="tns:simpleParam"/>
    <xs:element name="simpleReturn" type="tns:simpleReturn"/>
    <xs:complexType name="indirectReturn">
        <xs:sequence/>
    </xs:complexType>
    <xs:complexType name="simpleReturn">
        <xs:sequence/>
    </xs:complexType>
    <xs:complexType name="simpleParam">
        <xs:sequence/>
    </xs:complexType>
</xs:schema>

There is still some internal work to be done on caching; but the basics are in place. Feedback would be appreciated, particularly in cases where the code doesn't see the referenced classes. Finally thanks to Pavel Bucek for being patient as I learned the ropes.

Update 5th September 2011 This feature has been enabled by default so you no longer have to perform any of the WadlGeneration configuration when working with 1.9 final release of Jersey.

Wednesday, May 25, 2011

Jersey issue when running with ADF / Oracle XDK

We have discovered a late breaking bug in WLS that prevents Jersey applications from de-serializing XML and in some cases JSON documents. (So GET will work but PUT or POST will fail) This currently affects JDeveloper 11.1.1.5.0 aka PS4. You might see an exception that looks like this:

javax.xml.parsers.FactoryConfigurationError: WebLogicSAXParser
cannot be created.SAX feature
'
http://xml.org/sax/features/external-general-entities'
not
supported.
at weblogic.xml.jaxp.RegistrySAXParser.<init>(RegistrySAXParser.java:73)
at weblogic.xml.jaxp.RegistrySAXParser.<init>(RegistrySAXParser.java:46)
at
weblogic.xml.jaxp.RegistrySAXParserFactory.newSAXParser(RegistrySAXParserFacto
ry.java:91) 
...

When you create ADF components in an application you will find that at the .EAR level a weblogic-application.xml is created at deployment time which override the default WLS xml parser version to use the Oracle XDK version. You might see something like this:

<parser-factory>
<saxparser-factory>oracle.xml.jaxp.JXSAXParserFactory</saxparser-factory>
<document-builder-factory>oracle.xml.jaxp.JXDocumentBuilderFactory</document-builder-factory>
<transformer-factory>oracle.xml.jaxp.JXSAXTransformerFactory</transformer-factory>
</parser-factory>

When Jersey tries to convert a document to JAX-B classes it sensible sets a feature on the XML parser that prevents resolving external entities to prevent certain security attacks. This code in SAXParserContentProvider does understand that some parser don't support this feature as you can see:

@Override
    protected SAXParserFactory getInstance() {
        SAXParserFactory f = SAXParserFactory.newInstance();

        f.setNamespaceAware(true);
        
        if (!disableXmlSecurity) {
            try {
                f.setFeature("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
            } catch (Exception ex) {
                throw new RuntimeException("Security features for the SAX parser could not be enabled",  ex);
            }

            try {
                f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
            } catch (Exception ex) {
                LOGGER.log(Level.WARNING, 
                        "JAXP feature XMLConstants.FEATURE_SECURE_PROCESSING cannot be set on a SAXParserFactory. " +
                        "External general entity processing is disbaled but other potential securty related" +
                        " features will not be enabled.", 
                        ex);
            }
        }

        return f;
    }

This would normally be the end of it if it was not for bug 12543845, where the invalid features is incorrectly stored prevent the future creation of any parsers.

Luckily there is a workaround in Jersey, you can simply set this using a Servlet init param as follows:

<servlet>
    <servlet-name>jersey</servlet-name>
    <servlet-class>com.sun.jersey.
spi.container.servlet.ServletContainer</servlet-class>
   <init-param>

<param-name>com.sun.jersey.config.feature.DisableXmlSecurity</param-name>
      <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet> 

Be careful not to apply this property when you are not using the XDK parser otherwise you might open yourself up to a range of attacks which is not going to be a good thing.

Updated 30th March 2012: You might also need a similar workaround for the Jersey client as shown here. (I have raised this Jersey issue to see if this can be improved)

DefaultClientConfig cc = new DefaultClientConfig();
  cc.getFeatures().put(
    FeaturesAndProperties.FEATURE_DISABLE_XML_SECURITY, true);
  Client c = Client.create(cc);

Updated 15th August 2014: Slightly different for JAX-RS 2.0 client under Jersey, set this property on a configurable such as your client instance:

   configurable.property(MessageProperties.XML_SECURITY_DISABLE, Boolean.TRUE);


Thursday, March 11, 2010

Jersey 1.1.5.1 released

There is a minor point release of Jersey that includes a pair of significant bug fixes for weblogic and JDeveloper users. Worth using in preference to 1.1.5. Thanks a lot to Paul for putting this one together.

Tuesday, March 2, 2010

Building and testing Jersey

So I was playing with a patch Building an testing to Jersey but I was having trouble building and running the tests. It seems that there are problems if you don't have the same specific version of Maven, (2.0.9), once that is solved you might run across another issue as the default memory given to Maven will not be enough for all the test to succeed.

The way to track the correct values is to take a look at "hudson/jersey.sh" and this will set the MAVEN_OPTS to:

export MAVEN_OPTS="-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m"

This it turns out isn't sufficient if you are running behind a firewall, at least on Linux, and you need to specify the web proxy as well for some tests:

export MAVEN_OPTS="-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m -Dhttp.proxyHost=proxy -Dhttp.noProxyHosts=localhost"

With that resolved I can build and test all of Jersey in a relatively small amount of time. Although it does seem you need an active network connection which is a shame.

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, September 18, 2009

java.lang.reflect.Proxy client based on Jersey with a bit of HATEOAS built in

Many people prefer the kind of dynamic fluent API that Jersey provides for calling RESTful services. This doesn't suit every situation though and in some cases it would be nice to have a statically typed interface. RESTEasy provides something along these lines; but I wanted something that worked with Jersey and to take it a bit further to support basic HATEOAS.

So consider the following two interfaces and one bean class that make up the service we are trying to call. Note that there isn't a one to one mapping with the server classes as unlike with SOAP/WSDL you can be a bit flexible. It wouldn't make sense to generate a static client that supports both XML and JSON content types for example where-as the server would support both.

package bucketservice;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

@Path("buckets")
public interface Buckets {

    @POST
    public Bucket createNewBucketBound(String input);


    @GET
    @Produces("application/buckets+xml")
    public BucketList getBuckets();
    
    
    @Path("/{id}")
    public Bucket getBucket(@PathParam("id") String id);
}

Note there is a method for getting a bucket from an "id" property; but more likely you are going to want to create a resource directly from the URI. This you can do easily as we will see later.

The bucket itself is really simple as it just exposes the get and delete services.

package bucketservice;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;

public interface Bucket {

    @GET 
    public String getBucket();

    @DELETE 
    public Response.Status delete();

}

The bean class is straight forward enough but there is an extra annotation @MappedResource which is used to produce an extra method that is in terms of the resource rather than URIs. Currently this work is done with a Mk1 Human Generator but it wouldn't take too long to implement with with the APT.

package bucketservice;

import javax.xml.bind.annotation.XmlRootElement;

import proxy.MappedResource;


@XmlRootElement
public class BucketList 
{
    
    URI list[];

    public void setList(URI[] list) {
        this.list = list;
    }

    @MappedResource(Bucket.class)
    public URI[] getList() {
        return list;
    }
}

So the key part of the client example below is a static "of" method on ClientProxy that takes as it's input a web resource and the interface you want to use to talk to this resource and return a dynamic proxy based on the interface. This resource location shouldn't include any sub path information included on the interface.

The call to createNewBucketBound(...) results in a HTTP response of "201 Created" with the location of the resource as a URI. The proxy code knows the return type so will return a new proxy for the Bucket interface as determined by the return type of the method. You can then go off and happily invoke methods on this such as get or delete as if it was the real interface.

The rest of the method gets hold of a list of buckets, again these are dynamic proxies based on the interface given; deletes the resource, then show the list again to be sure.

package bucketclient;


import bucketservice.Bucket;
import bucketservice.BucketList;
import bucketservice.Buckets;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;

import static java.lang.System.out;

import java.net.URI;

import static proxy.ClientProxy.of;

import proxy.hateoas.HATEOASClientConfig;
 

public class BucketClient {
    public static void main(String[] args) {

        Client client = Client.create(new HATEOASClientConfig());
        WebResource rootResource = client.resource("http://localhost:7101/RSProxyClient-BucketService-context-root/jersey/");
        
        // Proxy

        Buckets buckets = of(rootResource, Buckets.class);
        out.println("Using proxy " + buckets);
        
        // Create me two buckets, note nothing is returned in the body of
        // the response message, just a location in the header. 
        //
        
        Bucket firstBucketRSP = 
            buckets.createNewBucketBound("First Bucket"); // POST -> 201
        Bucket secondBucketRSP = 
            buckets.createNewBucketBound("Second Bucket"); // POST -> 201
        
        // Get the contents of each
        //

        out.println("<Contents of buckets>");
        out.println(firstBucketRSP + "First # " 
           + firstBucketRSP.getBucket()); // GET .../id -> 200 text/plain
        out.println(secondBucketRSP + "# " 
           + secondBucketRSP.getBucket()); // GET .../id -> 200 text/plain
        out.println("</Contents of buckets>");
        
        // Get the list of buckets, use the injected getListAsResource method
        // to get bound interfaces
        //

        BucketList bucketList 
            = buckets.getBuckets(); // GET .../ -> 200 application/buckets+xml
        out.println("<Bucket List>");
        for (Bucket next : bucketList.getListAsResources()) {
            out.println("   " + next);
        }
        out.println("</Bucket List>");

        // Remove our buckets using the interface we had before
        //
        
        firstBucketRSP.delete(); // DELETE .../id -> 200
        secondBucketRSP.delete(); // DELETE ../id -> 200
        
        // Trace out bucket list again
        
        bucketList 
            = buckets.getBuckets(); // GET .../ -> 200 application/buckets+xml
        out.println("<Bucket List After Delete>");
        if (bucketList.getListAsResources()!=null)
        {
            for (Bucket next : bucketList.getListAsResources()) {
                out.println("   " + next);
            }
        }
        out.println("</Bucket List After Delete>");
        
    }
}

The getListAsResource(..) method as you might have noticed is not part of the BucketList interface and would be generated based on the @MappedResource annotation. How this this would happen I am not entirely sure; but you can start to see the start of a HATEOAS enabled client. Basically you can access the next resource along without any further work. You can imagine a "Transfer" bean that exposed the resources for the "Bank" at each end of the transfer. The client can deal with wiring this all up for you.

So the output of the run looks like this, note that all the Is-A object are dynamic proxies of the resource.

Using proxy Is-A:bucketservice.Buckets@[uri=http://localhost:7101/RSProxyClient-BucketService-context-root/jersey/buckets]
<Contents of buckets>
Is-A:bucketservice.Bucket@[uri=http://localhost:7101/RSProxyClient-BucketService-context-root/jersey/buckets/2]First # First Bucket
Is-A:bucketservice.Bucket@[uri=http://localhost:7101/RSProxyClient-BucketService-context-root/jersey/buckets/3]# Second Bucket
</Contents of buckets>
<Bucket List>
   Is-A:bucketservice.Bucket@[uri=http://localhost:7101/RSProxyClient-BucketService-context-root/jersey/buckets/2]
   Is-A:bucketservice.Bucket@[uri=http://localhost:7101/RSProxyClient-BucketService-context-root/jersey/buckets/3]
</Bucket List>
<Bucket List After Delete>
</Bucket List After Delete>

I hope you can see by example how easy it is to convert a URI into a strongly typed interface.

   URI bucket = ....
   Bucket bucketIF = of(client.resource(bucket), Bucket.class);

Ideally the interfaces would be generated from a sub set of a WADL, you would want to be able to filter by content-type and resource path. Again this kind of focused generation would have been much harder with JAX-WS / WSDL.

The code for this is still on my laptop, except for the code generation, and unfortunately it is not something I can distribute at the moment. This is something I am going to look into if people find this approach interesting.

Thursday, September 10, 2009

Validating annotations at compile time, example using JAX-RS

I am in the process of re-reading Effective Java now that I have gotten around to buying the second edition. I always enjoy reading anything that Bloch puts out and I always learn something new. I was working my way through item 35 "Prefer Annotations to Naming conventions" when I noticed the following statemen that was talking about validating the annotations:

"... It would be nice if the compiler could enforce this restriction, but it can't. There are limits to how much error checking the compiler can do...."

Now it is normally very hard find something that you think that Mr Bloch has got wrong, and also be right; but I think this validation is very possible. Recently I have been looking at Project Lombok which does interesting things with the annotation processor. The general idea behind the annotation processors is that they give the ability to generate new code; but it occurred to me that an annotation processor can just check source files for errors. This allows you to extend the java compiler to do interesting non trivial annotation validation.

Rather than deal with the simple @Test example in the book, I have a solution for that one if anybody in interested, lets instead look at a real world examples from JAX-RS web services:

package restannotationtotest;

import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;

public class ExampleResource {

    @GET
    public String goodGetNoParam() {
        return "Hello";
    }

    @GET
    public String goodGetParam(@QueryParam("name")String name) {
        return "Hello " + name;
    }
    
    // This annotation will fail at deploy time    
    @GET
    public String badGet(String name) {
        return "Hello " + name;
    }
}

The last method will fail at deploy time as a HTTP GET request cannot have a method body as implied by having a method parameter that is not otherwise consumed by the framework. This is a mistake I kept on making when I started with Jersey so I though it was worth starting with. So our first goal is to flag this last method up at compile time as being in error.

In order for an annotation processor to work you need a jar file with an entry in the META-INF/services path called javax.annotation.processing.Processor. This contain a list of fully qualified processor class names in plain text. In some tools you might find that the javax.annotation.processing.Processor file is not copied to the classpath as you need to add ".Processor" to the list of file extensions copied at compile time. You also of course need a class that implements the Processor interface so my project looks like this:

First of all lets look at the basic house keeping parts of the JaxRSValidator class without worry about the meat of the class. We extend the AbstractProcessor rather than implementing the Processor interface both to gain some basic functionality and to future proof the code against interface evolution. We could have used the @SupportedAnnotationTypes annotation rather than implement the corresponding method; but for later it was handy to have a list of class literals. As you can see there is not much to the configuration:

package com.kingsfleet.rs;


import java.lang.annotation.Annotation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementScanner6;
import javax.lang.model.util.Types;

import javax.tools.Diagnostic;

import javax.ws.rs.CookieParam;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;


@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class JaxRSValidator extends AbstractProcessor {

    private static final List<Class<? extends Annotation>> PARAMETER_ANNOTATIONS
                     = Arrays.asList(
                           CookieParam.class,
                           FormParam.class,
                           HeaderParam.class,
                           MatrixParam.class,
                           PathParam.class,
                           QueryParam.class, 
                           Context.class,
                           Resource.class);
     
    private static final List<Class<? extends Annotation>> METHOD_ANNOTATIONS
                    = Arrays.asList(
                          GET.class,
                          POST.class,
                          PUT.class,
                          DELETE.class,
                          HEAD.class,
                          OPTIONS.class);

    private static final Set<String> MATCHING_ANNOTATIONS_AS_STRING;
    static {
        Set<String> set = new HashSet<String>();
        for (Class a : METHOD_ANNOTATIONS) {
            set.add(a.getName());
        }
        // We care about path as well
        //
        set.add(Path.class.getName());
        //
        MATCHING_ANNOTATIONS_AS_STRING = Collections.unmodifiableSet(
                                           set);
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return MATCHING_ANNOTATIONS_AS_STRING;
    }

  
    // Implementation of processor
    //

}

The actual implementation of the processor is in the process method, you will notice that for most annotation processors all the work is done using a visitor or the concrete scanner implementations.

The scanner we have implemented looks for an ExecutableElement that is of type "Method". The javax.lang.model is a little bit obscure in its terminology so it did take me a little bit of time to work out that this is the correct structure. Another limitation is that you cannot drill down into the finer details of the code structure without casting this a class from com.sun.tools.javac.code.Symbol: with attendant maintenance issues of using a com.sun.* package. Fortunately for the validation I want to do I can stick with the public APIs. Hopefully a future version of Java will expand on this API.

It is a relatively simple matter after that to process each method in turn looking for a one with the @GET annotation that would suggest a message body of some kind.

    private static ElementScanner6<Void, ProcessingEnvironment> SCANNER =
        new ElementScanner6<Void, ProcessingEnvironment>() {
        @Override
        public Void visitExecutable(ExecutableElement e,
                                    ProcessingEnvironment processingEnv) {

            final Messager log = processingEnv.getMessager();
            final Types types = processingEnv.getTypeUtils();

            // Make sure for a GET all parameters are mapped to
            // to something sensible
            //

            if (e.getKind() == ElementKind.METHOD) {
                
                // GET no body
                verifyNoBodyForGET(e, log);
            }
            return null;
        }


        /**
         * Check that if we have a GET we should have no body. (Should
         *   also process OPTIONS and others)
         */
        private void verifyNoBodyForGET(ExecutableElement e,
                                        final Messager log) {
            if (e.getAnnotation(GET.class) != null) {
                
                // For each parameter check for the standard annotations
                found : for (VariableElement ve : e.getParameters()) {
                    for (Class<? extends Annotation> c : PARAMETER_ANNOTATIONS) {
                        if (ve.getAnnotation(c)!=null) {
                            break found;
                        }
                    }
                    
                    log.printMessage(Diagnostic.Kind.ERROR, 
                                     "Parameters on a @GET cannot be mapped to a request body, try one of the @*Param annotations",
                                     e);
                }
            }
        }
    };

    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment roundEnv) {

        for (TypeElement annotation : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(annotation)) {
                SCANNER.scan(e, processingEnv);
            }
        }

        // Allow other processors in
        return false;
    }

It is important when you use the log.printMessage(...) method to include the Element as the final parameter as this allows tooling to correctly display the error/warning message location. So it is a simple matter to build this project into a jar file using the tool of your choice and then have it on the the classpath when you build ExampleResource that we defined earlier. (JDeveloper users note my previous post on Lombok on running javac "Out of Process" to get this working). Depending on your tool you should get an error message that looks something like this:

Lets look at a more complicated example from Jersey to build on our code. In this example we need the parameters in the @Path annotation to match @PathParam parameters on the matching method. In this case here are two that are fine and two that might fail at some point later on. Neither of the problems can be picked up by the compiler.

package restannotationtotest;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

public class PathResource {
    
    // Fine
    @Path("{param}")
    public ExampleResource getOneParam(@PathParam("param") String param) {
        return null;
    }

    // Fine
    @Path("{param}/someotherText/{param1}")
    public ExampleResource getOneParam(@PathParam("param") String param, @PathParam("param1") String param1) {
        return null;
    }

    // Suspect
    @Path("{param}")
    public ExampleResource getUnusedParam() {
        return null;
    }
    
    // Definitely broken
    @Path("{param}")
    public ExampleResource getMissMatch(@PathParam("paramMissMatch") String param) {
        return null;
    }
}

Our original code already visits all the methods so we need to simply extend the code to check that the parameters match entries on the @Path. We can also check for a zero length @PathParam argument which again is something that compiler can't do for free.

    private static ElementScanner6<Void, ProcessingEnvironment> SCANNER =
        new ElementScanner6<Void, ProcessingEnvironment>() {
        @Override
        public Void visitExecutable(ExecutableElement e,
                                    ProcessingEnvironment processingEnv) {

            final Messager log = processingEnv.getMessager();
            final Types types = processingEnv.getTypeUtils();

            // Make sure for a GET all parameters are mapped to
            // to something sensible
            //

            if (e.getKind() == ElementKind.METHOD) {
                
                // GET no body
                verifyNoBodyForGET(e, log);
                
                // Try to match path param to @Path
                verifyPathParamMatches(e, log);
            }
            return null;
        }

        /**
         * Check that if we have path param we have all the matching
         * path elements consumed.
         */
        private void verifyPathParamMatches(ExecutableElement e,
                                            final Messager log) {
            
            // Verify that we have a method that has resource
            //
            
            Path p = e.getAnnotation(Path.class);
            if (p!=null && p.value()!=null) {
                
                // Hack the resources out of the string, verify
                // path parameters, TODO write regex
                //
                List<String> resources = new ArrayList<String>();
                String path = p.value();
                final String[] splitByOpen = path.split("\\{");
                for (String bit : splitByOpen) {
                    String moreBits[] = bit.split("}");
                    if (moreBits.length >= 1 && moreBits[0].length() !=0) {
                        resources.add(moreBits[0]);
                    }
                }
                
                // If we have resource try to find path params to match
                if (resources.size() > 0) {
                    found : for (VariableElement ve : e.getParameters()) {

                        PathParam pp = ve.getAnnotation(PathParam.class);
                        String mappedPath = pp.value();
                        if (mappedPath==null || mappedPath.length()==0) {
                            log.printMessage(Diagnostic.Kind.ERROR, 
                                             "Missing or empty value",
                                             ve);
                        }
                        else if (!resources.contains(mappedPath)) {
                            log.printMessage(Diagnostic.Kind.WARNING, 
                                             "Value " + mappedPath + " doesn't map to path",
                                             ve);
                        }
                        else {
                            // Make this as processed
                            resources.remove(mappedPath);
                        }
                    }
                    
                    if (resources.size() > 0) {
                        log.printMessage(Diagnostic.Kind.WARNING, 
                                         "Unmapped path parameters " + resources.toString(),
                                         e);
                    }
                }
            }
        }

        /**
         * Check that if we have a GET we should have no body. (Should
         *   also process OPTIONS and others)
         */
        private void verifyNoBodyForGET(ExecutableElement e,
                                        final Messager log) {
            ...
        }
    };

Again you can simple compile the project with ExampleResource and PathResources using your favorite build tool and you should see something like:

Note the second method gets two warnings, the line numbers are the same.

So this contrary to the original statement it is possible to get the compilers to perform quite complex validation of annotations. I wonder if we could convince some of the annotation based JEE projects and libraries to come with a validation jar file that contains a matching processor to validate against the spec. It would save a lot of confusion. Tools developers like myself would also be able to harness the same code in code editors to provide in line error feedback in a consistent way. Interesting stuff.