JAX-RS - Accessing ContextResolver in another @Provider class

[Updated: Jul 4, 2017, Created: Jul 3, 2017]

In the last tutorial, we learnt how to use ContextResolver with a very simple example. In this tutorial, we will briefly discuss in what cases this feature should be used. We will then see an example of accessing ContextResolver in another @Provider class.

JAX-RS specifications does not go into details about use cases for using ContextResolver. Following are the lines from the specification JSR 339 (4.3):

E.g., an application wishing to provide a customized JAXBContext to the default JAXB entity providers would supply a class implementing ContextResolver<JAXBContext>.

Context providers MAY return null from the get Context method if they do not wish to provide their context for a particular Java type. E.g. a JAXB context provider may wish to only provide the context for certain JAXB classes. Context providers MAY also manage multiple contexts of the same type keyed to different Java types.

We can look at Jersey source code as well to find out how ContextResolver are implemented there.

In following example, we will apply the similar pattern as JAXBContext in Jersey does (for example check out this source code) but in a very simple form. We are going to reuse our previous example of YAML Entity provider and will write a ContextProvider implementation for a YAML context. Let's see how to do that:

Example

Writing the Context Provider

public interface YamlContext {

  <T> T readEntity(InputStream entityStream, Class<T> type) throws IOException;

  void writeEntity(OutputStream entityStream, Object t) throws IOException;
}
@Provider
public class YamlContextResolver implements
        ContextResolver<YamlContext> {
    private YamlContext defaultContext = new SnakeYamlContext();

    @Override
    public YamlContext getContext(Class<?> type) {
        //we can restrict the return value based on type
        return defaultContext;
    }
}
public class SnakeYamlContext implements YamlContext {
  @Override
  public <T> T readEntity(InputStream entityStream, Class<T> type) throws IOException {
      Yaml yaml = new Yaml();
      T t = yaml.loadAs(toString(entityStream), type);
      return t;
  }

  @Override
  public void writeEntity(OutputStream entityStream, Object t) throws IOException {

      Yaml yaml = new Yaml();
      OutputStreamWriter writer = new OutputStreamWriter(entityStream);
      yaml.dump(t, writer);
      writer.close();
  }

  private static String toString(InputStream inputStream) {
      return new Scanner(inputStream, "UTF-8")
              .useDelimiter("\\A").next();
  }
}

Writing YAML entity Provider

Instead of using SnakeYAML directly here, we will use our YamlContextResolver:

@Provider
@Consumes({"application/yaml", MediaType.TEXT_PLAIN})
@Produces({"application/yaml", MediaType.TEXT_PLAIN})
public class YamlEntityProvider<T> implements MessageBodyWriter<T>, MessageBodyReader<T> {
    @Context
    private Providers providers;

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
                              MediaType mediaType) {
        return true;
    }

    @Override
    public T readFrom(Class<T> type, Type genericType, Annotation[] annotations, MediaType mediaType,
                      MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
            throws IOException, WebApplicationException {
        ContextResolver<YamlContext> cr = providers.getContextResolver(YamlContext.class, mediaType);
        YamlContext context = cr.getContext(type);
        T t = context.readEntity(entityStream, type);
        return t;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
                               MediaType mediaType) {
        return true;
    }

    @Override
    public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations,
                        MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations,
                        MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
                        OutputStream entityStream) throws IOException, WebApplicationException {
        ContextResolver<YamlContext> cr = providers.getContextResolver(YamlContext.class, mediaType);
        YamlContext context = cr.getContext(type);
        context.writeEntity(entityStream, t);

    }
}

Writing a Resource

@Path("employees")
public class EmployeeResource {
    private static Set<Employee> employees = new HashSet<>();

    @PUT
    @Consumes("application/yaml")
    @Path("/{newId}")
    public String create(Employee employee,
                         @PathParam("newId") long newId) {
        employee.setId(Long.valueOf(newId));
        if (employees.contains(employee)) {
            throw new RuntimeException("Employee with id already exists: "
                    + newId);
        }
        employees.add(employee);
        return "Msg: employee created for id: " + newId;
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public List<Employee> list() {
        return new ArrayList<>(employees);
    }

    @DELETE
    public void deleteAll(){
          employees.clear();
    }
}

To try examples, run embedded tomcat (configured in pom.xml of example project below):

mvn tomcat7:run-war

Writing a JAX-RS client

public class EmployeeClient2 {
  private static Client client = ClientBuilder.newBuilder()
                                              .register(YamlEntityProvider.class)
                                              .register(YamlContextResolver.class)
                                              .build();
  private static WebTarget baseTarget = client.target("http://localhost:8080/employees");

  public static void main(String[] args) {
      reset();
      createEmployees();
      listEmployees();
  }

  private static void listEmployees() {
      System.out.println("-- getting employee list --");
      List<Employee> employees = baseTarget.request().get(
              new GenericType<List<Employee>>() {
              });
      employees.forEach(System.out::println);
  }

  private static void createEmployees() {
      System.out.println("-- making PUT requests --");

      List<String> deptList = Arrays.asList("Admin", "IT", "Sale");
      for (int i = 1; i <= 5; i++) {
          Employee e = new Employee();
          e.setName("Employee" + i);
          int index = ThreadLocalRandom.current()
                                       .nextInt(0, 3);
          e.setDept(deptList.get(index));

          WebTarget target = baseTarget.path(Integer.toString(i));
          Response r = target.request()
                             .put(Entity.entity(e, "application/yaml"));
          System.out.println(r.readEntity(String.class));
      }
  }

  private static void reset() {
      baseTarget.request().delete();
  }
}

Output

-- making PUT requests --
Msg: employee created for id: 1
Msg: employee created for id: 2
Msg: employee created for id: 3
Msg: employee created for id: 4
Msg: employee created for id: 5
-- getting employee list --
Employee{id=1, name='Employee1', dept='Sale'}
Employee{id=2, name='Employee2', dept='Sale'}
Employee{id=3, name='Employee3', dept='Sale'}
Employee{id=4, name='Employee4', dept='Admin'}
Employee{id=5, name='Employee5', dept='Sale'}

Example Project

Dependencies and Technologies Used :

  • jersey-server 2.25.1: Jersey core server implementation.
  • jersey-container-servlet 2.25.1: Jersey core Servlet 3.x implementation.
  • snakeyaml 1.18: YAML 1.1 parser and emitter for Java.
  • JDK 1.8
  • Maven 3.3.9

YAML ContextResolver Example Project Select All Download
  • jaxrs-yaml-context-resolver
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also