How to write an XML MessageBodyWriter provider with jersey

11,204

Typically one would use a MessageBodyWriter to convert objects to data formats that Jersey knows nothing about. Here's an example of translating a TableData domain object to CSV:

@Singleton
@Produces("text/csv")
@Provider
public class FederationCsvWriter implements MessageBodyWriter<TableData> {

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

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

  @Override
  public void writeTo(TableData data, Class<?> type, Type genericType, Annotation[] annotations,
      MediaType mediaType, MultivaluedMap<String, Object> headers, OutputStream out) throws IOException {
    Writer osWriter = new OutputStreamWriter(out);
    CSVWriter writer = new CSVWriter(osWriter, ',', '"', "\r\n");
    if (data.getResultCount() > 0) {
      //Write the header
      writer.writeNext(data.getResult().get(0).keySet().toArray(new String[data.getResult().get(0).keySet().size()]));

      //Write the data
      for (ModelData row: data.getResult()) {
        writer.writeNext(row.values().toArray(new String[row.values().size()]));
      }
    }
    writer.flush();
  }

}

In your example you could either create XML by inspecting the object yourself and feeding the result to the OutputStream or your could use JAXB (which Jersey uses internally) to marshall the objects directly to the OutputStream. For the purposes of your exercise JAXB is probably more interesting.

Share:
11,204
Leonardo
Author by

Leonardo

Updated on June 07, 2022

Comments

  • Leonardo
    Leonardo almost 2 years

    I am not trying to solve any problem in particular, but rather I am on a learning path to jersey.

    I have an entity class marked like this:

    @Entity
    @Table(name = "myentity")
    @XmlRootElement
    public class MyEntity implements serializable {
      // lots of methods...
    }
    

    and the corresponding jersey service

    @Stateless
    @Path("entity")
    public class EntityFacade {
    
        @GET
        @Path("{param}")
        @Produces({"application/xml;charset=UTF-8"})
        public List<MyEntity> find(@PathParam("param") String param) {
           List entities = entityManager.getResultList(); // retrieve list from db
           return entities;
        }
    
    }
    

    Which give a correct XML response. Supposing I want to write a MessageBodyWriter which replicate same behavior, which is producing an XML response, how could I do that ?

    @Provider
    public class TrasformerMessageBodyWriter implements MessageBodyWriter<Object> {
    
        @Override
        public long getSize(Object o, Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType) {
            return 0;
        }
    
        @Override
        public boolean isWriteable(Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType) {
            // return true or false depending if I want to rewrite the response
        }
    
        @Override
        public void writeTo(Object o, Class<?> type, Type genericType,
                Annotation[] annotations, MediaType mediaType,
                MultivaluedMap<String, Object> httpHeaders,
                OutputStream entityStream) throws IOException,
                WebApplicationException {
            // what do I need to write here...
        }
    }
    

    by marking with @Provider annotation I can see the message body writer is correctly invoked.

    When writeTo is invoked, Object o is a Vector and Type genericType is a List but at this point I am completely lost on how I could transform the object in XML.

    Last, if everything is already provided by jersey and its annotations, how can a MessageBodyWriter be useful ?

    Again, I am repeating that this is just an academic exercise.

  • Leonardo
    Leonardo almost 11 years
    Using JAXB Unless I write a wrapper class, the List is returned by the web service as a Vector<MyEntity> therefore I cannot marshall it.
  • Stony
    Stony almost 11 years
    Can you explain that getSize should return -1 ?
  • condit
    condit almost 11 years
    @StonyZhang - From the javadoc ( goo.gl/XyrGV ): Called before writeTo to ascertain the length in bytes of the serialized form of t. A non-negative return value is used in a HTTP Content-Length header.
  • Stony
    Stony almost 11 years
    Thanks a lot for your clarification.
  • mdarwin
    mdarwin about 9 years
    According to the spec you're not supposed to close the OutputStream. Will closing the writer not close the OutputStream too?
  • Nelda.techspiress
    Nelda.techspiress almost 7 years
    The -1 return value for getSize() is due to changes in JAX-RS and getSize() has been deprecated. As of JAX-RS 2.0, the method has been deprecated and the value returned by the method is ignored by a JAX-RS runtime. All MessageBodyWriter implementations are advised to return -1 from the method. Responsibility to compute the actual Content-Length header value has been delegated to JAX-RS runtime. Here is a new link to the above reference by @condit.