Close

JAX-RS - Custom Entity Provider for YAML conversion

[Updated: Jun 28, 2017, Created: Jun 27, 2017]

In the last tutorial, we got familiar with the concept of a Provider in JAX-RS. In this tutorial, we will create a custom Entity Provider. This provider will convert request body containing YAML content to user defined Java object and vice-versa.

We are going to use SnakeYAML, which is a Java based processor for parsing YAML data to/from Java Objects.

Writing the Entity provider

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

  @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 {
      Yaml yaml = new Yaml();
      T t = yaml.loadAs(toString(entityStream), type);
      return t;
  }

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

  @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 {
      Yaml yaml = new Yaml();
      OutputStreamWriter writer = new OutputStreamWriter(entityStream);
      yaml.dump(t, writer);
      writer.close();
  }
}

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();
  }
}
public class Employee {
  private long id;
  private String name;
  private String dept;
    .............
}

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

mvn tomcat7:run-war

Writing JAX-RS client

public class EmployeeClient {
  private static Client client = ClientBuilder.newClient();
  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 --");
      String response = baseTarget.request().get(String.class);
      System.out.println(response);
  }

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

      String[] employeesInYml = {
              "name: employee1\ndept: IT\n",
              "name: employee2\ndept: Admin\n",
              "name: employee3\ndept: Sale\n"
      };

      for (int i = 0; i < employeesInYml.length; i++) {
          WebTarget target = baseTarget.path(Integer.toString(i));
          Response r = target.request()
                             .put(Entity.entity(employeesInYml[i], "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: 0
Msg: employee created for id: 1
Msg: employee created for id: 2
-- getting employee list --
- !!com.logicbig.example.Employee {dept: Admin, id: 1, name: employee2}
- !!com.logicbig.example.Employee {dept: Sale, id: 2, name: employee3}
- !!com.logicbig.example.Employee {dept: IT, id: 0, name: employee1}

Registering JAX-RS provider on the client side

In above example client, we used plain YAML instead of our Employee object for requests and responses, that's because there's no scanning phase on the client side and our YamlEntityProvider will not be discovered and registered with JAX-RS Client. There, we have to register the provider manually by using ClientBuilder#register(....) method:

public class EmployeeClient2 {
  private static Client client = ClientBuilder.newBuilder().register(new YamlEntityProvider<>()).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='Admin'}
Employee{id=4, name='Employee4', dept='IT'}
Employee{id=5, name='Employee5', dept='IT'}

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 Entity Provider Select All Download
  • yaml-entity-provider
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also