简体   繁体   中英

Jersey JSON serialization with external classes

I am trying to create a rest api server on top of my quartz scheduler. I want to be able to return the org.quartz.Trigger and org.quartz.JobDetail objects as JSON. The problem is that I cannot add the @XmlRootElement to these classes without having to recompile the jar and this causes problems with future upgrades etc. I have tested and am able to serialize a list of classes when adding the @XmlRootElement but when I try to return a List I get the error "A message body writer for Java class java.util.ArrayList, and Java type java.util.List, and MIME media type application/json was not found". I have tried adding a custom MessageBodyWriter but that does not seem to fix the problem either. Is there a way to marshal the quartz classes to JSON without having to modify the quartz code to add the @XmlRootElement. I am using this in an embedded web server with jetty btw.

@Path("/jobs")
public class JobsResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Trigger> listScheduledJobs() throws SchedulerException {
        return TaskEngine.getInstance().listScheduledJobs();
    }

}

Web server configuration

public class TaskEngineWebServer {

    private static final Logger logger = Logger.getLogger(TaskEngineWebServer.class.getName());

    private Server server;

    public TaskEngineWebServer() {
        this(8585);
    }

    public TaskEngineWebServer(Integer port) {
        server = new Server(port);

        logger.info("Configuring rest service to start at url /r");
        ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.NO_SECURITY);
        //handler.getInitParams().put("com.sun.jersey.api.json.POJOMappingFeature", "true");
        PackagesResourceConfig packagesResourceConfig = new PackagesResourceConfig("com.hp.vf.scheduler.server.rest");

        ServletContainer servletContainer = new ServletContainer(packagesResourceConfig);
        handler.addServlet(new ServletHolder(servletContainer), "/r/*");

        server.setHandler(handler);
        logger.info("Done configuring rest service");
    }

    public void start() throws Exception {
        server.start();
    }

    public void stop() throws Exception {
        server.stop();
    }

    public boolean isStarted() {
        return server.isStarted();
    }

    public boolean isStopped() {
        return server.isStopped();
    }
}

I dont think you can return a List as JSON directly. You need to have a wrapper class which contains this list. For eg try something like this

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class TriggerWrapper{

  private List<Triggers> triggers;

  public List<Triggers> getTriggers(){
   if(triggers==null){
     triggers = new ArrayList<Triggers>();
   }
   return triggers;
  }
}

Then in your rest service class :

@Path("/jobs")
public class JobsResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public TriggerWrapper listScheduledJobs() throws SchedulerException {
        TriggerWrapper response = new TriggerWrapper();
        List<Triggers> triggers =  TaskEngine.getInstance().listScheduledJobs();
        response.getTriggers.addAll(triggers);
        return response;
    }

}

Your json would something like this :

{
    "triggerwrapper": {
        "triggers": [
            {
                "triggerid": 1
            },
            {
                "triggerid": 2
            }
        ]
    }
}

And ofcourse if you want you can drop the root element tag from your json its configurable in jersey.

I finally figured out a clean solution, it involves creating my own MediaBodyWriter class and adding it as a provider. You have to make sure you are not using the jersey-bundle jar as the default jaxb to json provider will override yours.

jars required

jersey-core jersey-servlet jersey-server

jackson-annotations jackson-databind jackson-core

I found this MediaWriter example on the web somewhere. Sorry for not having the url but thanks to whoever write it.

@Provider
@Produces({ MediaType.APPLICATION_JSON })
public class JacksonWriter implements MessageBodyWriter<Object> {
    private static final ObjectMapper MAPPER = new ObjectMapper();

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

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

    @Override
    public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
            OutputStream entityStream) {
        try {
            MAPPER.writeValue(entityStream, value);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}

When it loads you will see a log message that your provider was loaded.

This gave me the json output I was expecting as it does not rely on the JAXB annotations and simply uses the object mapper/ reflection. Probably less efficient but for my case it does not matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM