Showing posts with label JavaEE. Show all posts
Showing posts with label JavaEE. Show all posts

Monday, August 31, 2015

A simple CSV MessageBodyWriter for JAX-RS using Jackson

This is a very simple MessageBodyWriter that will allow you to output a List of objects as CSV from a JAX-RS webservice. Such services can be useful with frameworks such as D3.js. Jackson provides MessageBodyWriters for several formats, but it does not provide an out of the box solution for CSV. It does however offer several useful  classes to serialize objects into CSV. These are provided in the jackson-dataformat-csv artifact.
We can use those classes to create our own CSV MessageBodyWritter as shown below:
package csv;


import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;

@Provider
@Produces("text/csv")
public class CSVMessageBodyWritter implements MessageBodyWriter {

    @Override
    public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        boolean ret=List.class.isAssignableFrom(type);
        return ret;
    }

    @Override
    public long getSize(List data, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return 0;
    }

    @Override
    public void writeTo(List data, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
        if (data!=null && data.size()>0) {
            CsvMapper mapper = new CsvMapper();
            Object o=data.get(0);
            CsvSchema schema = mapper.schemaFor(o.getClass()).withHeader();
            mapper.writer(schema).writeValue(outputStream,data);
        }


    }

}
To use our MessageBodyWriter, it must be registered. This can achieved in several ways, depending on your JAX-RS implementation. Normally Jersey and other JAX-RS implementations are configured to scan packages and look for resources. In such cases classed marked with @Provider will be registered automatically. In other cases, the registration will have to be done manually. For example in Dropwizard, you have to manually register the Writer at startup:
 @Override
    public void run(MyConfiguration configuration,
                    Environment environment) {
      
        environment.jersey().register(new CSVMessageBodyWritter());
    }
Once registered, it becomes trivial to have a web service that outputs CSV. It's just a matter of annotating your webservice with the same media type as the MessageBodyWritter: @Produces("text/csv").
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.List;

@Path("/status")
public class StatusResource {
    @GET
    @Produces("text/csv")
    public List<Data> getData() {
        List<Data> data= service.getStatus();
        return data;
    }
}
Our data class is just a normal POJO:
public class Data {
    private String date;
    private Integer minimum;
    private Integer maximum;
    private Integer average;

    public Data() {

    }

    //Getters and setters as needed
}
The output will look like this:
average,date,maximum,minimum
90,3/1856,125,0
60,2/1856,115,16
60,4/1856,115,16

This is a very simple implementation, but should be a good starting point. It's worth nothing that CSV is inherently limited, and can't easily represent hierarchical object graphs. Therefore you might need flatten your data before exporting to CSV. If you need to ingest a CSV in a webservice, you can follow a similar approach to create a MessageBodyReader that will create an object from a CSV stream.

Sunday, March 1, 2015

Stepping through the internal code of your JavaEE application server in IntelliJ IDEA

While while trying to container based authentication working with a JDBC realm, I had the need to step through the internals of GlassFish to get a better idea why my settings were not working. It seems that by default IntelliJ IDEA does not provide access to the class path of the application server.


To be able to step through the code, I had to follow a few steps:


1 - Increase the logging in GlassFish.  This is optional, but it gave me an idea of where to start looking for the issue.
2 - Download the GlassFish source code.  This is not an issue for an open source app server.  If you don't have access to the source of your particular proprietary app server, IntelliJ IDEA will still allow you to step through it, and will try to decompile the code, with various level of sucess.
3 - Add the jar that contains the actual compiled byte code.  I found two ways of doing this for Maven based project, one using the provided scope, and another way using the system scope. This step is critical, and at least for me counter intuitive.

Add the jar to your pom.xml using the provided scope:
<dependency>
    <groupId>org.glassfish.main.extras</groupId>
    <artifactId>glassfish-embedded-all</artifactId>
    <version>4.1</version>
    <scope>provided</scope>
</dependency>

This is the easiest solution, but you might not be able to find the required jar in any maven repo.  If you don't find the right jar, but find any jar that has the class, you can use.  For this to work you will have to attach the right source code, as downloaded in step #2.  Otherwise, IntelliJ will decompile the class and give you a nonsensical java file that does not match what you are running.  


Add the jar to your pom.xml using the system scope:
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>glassfish</artifactId>
    <version>4.1</version>
    <systemPath>h:/servers/glassfish/modules/security-ee.jar</systemPath>
    <scope>system</scope>
</dependency>

If you don't have source code, I recommend this approach.  This will give you a lot of information, even if you lack access to the source code.

5 - Add the source to your new library. The easiest way is to open the class that you want to examine or add the break point to.  Once open you will see the source as decompiled by IntelliJ IDEA. If possible add the sources you downloaded in step #2 to make your life a bit easier.  Select "Choose Sources..." and navigate to your source directory.

You now have access to the class you need. Drop a break point, add a watch, step through it as you need!

6 - Make sure you remove the dependency after your done.  You don't want to have extra dependencies, and in the worst case the build will fail in other computers, if the system scoped library is not available in the same location.