Generic Response handling in REST java

Generic Response handling in REST java



I have created the REST response as below


@XmlRootElement(name = "customer_info")
@JsonIgnoreProperties(value = "map" )
public class Customer
private String name;
private Integer id;
private long time;
private Map<String, Object> map = new TreeMap<>();
@JsonAnyGetter
public Map<String, Object> getMap()
return map;


@JsonAnySetter
public void setMap(String name, Object values)
this.map.put(name, values);




I want to dynamically create this response with addional parameter.The backend returns a map which will contains addional prop for this.


<customer_info>
<name></name>
<id></id>
<!--The below will have dynamic prop based on key-->
<dynamic1></dynamic1>
<dynamic2></dynamic2>
</customer_info>



The application config


ResourceConfig config = new DefaultResourceConfig();
config.add(resources);
Map<String, MediaType> type = config.getMediaTypeMappings()
type.put("json", MediaType.APPLICATION_JSON_TYPE);
type.put("xml", MediaType.APPLICATION_XML_TYPE);
servletHandler.addServlet(new ServletHolder(new ServletContainer(config)), "/*");



When i add the Map the xml structure appears as below which is not correct.I need the above xml structure.Can someone guide me how to implement this?



Also the name of the XML element should be same as the key in the map.The JSON response works fine but xml structure is not correct.


<customer_info>
<name></name>
<id></id>
<!--The below will have dynamic prop based on key-->

<map>
<entry>
<key>dummy_param1</key>
<value>11</value>
</entry>
<entry>
<key>dummy_param2</key>
<value>10</value>
</entry>
<map>
</customer_info>



I am using Jersey as REST framework and its running in Jetty server.
I am using the provider and getting it registered using springs along with the REST service.


<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
<bean class="com.dummy.CustomerService"/>
<bean class="com.dummy.Service2"/>
<bean class="com.dummy.Service3"/>



The library gradle config is as below:


"org.codehaus.jackson:jackson-core-asl:1.9.2",
"org.codehaus.jackson:jackson-jaxrs:1.9.2",
"org.codehaus.jackson:jackson-mapper-asl:1.9.2",
"org.codehaus.jackson:jackson-xc:1.9.2",
"com.sun.jersey:jersey-client:1.12",
"com.sun.jersey:jersey-core:1.12",
"com.sun.jersey:jersey-json:1.12",
"com.sun.jersey:jersey-server:1.12",
"com.sun.jersey:jersey-servlet:1.12",
"org.codehaus.jettison:jettison:1.1",
"javax.ws.rs:jsr311-api:1.1.1",
"com.sun.jersey.contribs:jersey-apache-client:1.12",
"com.sun.jersey.contribs:jersey-apache-client4:1.12",
"com.sun.jersey.contribs:jersey-multipart:1.12",
"com.sun.jersey:jersey-client:1.12",
"com.sun.jersey:jersey-core:1.12",
"com.sun.jersey:jersey-json:1.12",
"javax.ws.rs:jsr311-api:1.1.1",
"org.codehaus.jackson:jackson-core-asl:1.9.2",
"org.codehaus.jackson:jackson-jaxrs:1.9.2",
"org.codehaus.jackson:jackson-mapper-asl:1.9.2",
"org.codehaus.jackson:jackson-xc:1.9.2",
"javax.servlet:javax.servlet-api:3.1.0",
"org.eclipse.jetty:ecs-jetty-server:9.4.0.v20161208",
"org.eclipse.jetty:jetty-util:9.4.0.v20161208",
"org.eclipse.jetty:jetty-servlet:9.4.0.v20161208",
"org.eclipse.jetty:jetty-servlets:9.4.0.v20161208",
"org.eclipse.jetty:jetty-http:9.4.0.v20161208",
"org.eclipse.jetty:jetty-security:9.4.0.v20161208",
"org.eclipse.jetty:jetty-io:9.4.0.v20161208",
"org.eclipse.jetty:jetty-continuation:9.4.0.v20161208",
"org.eclipse.jetty:jetty-deploy:9.4.0.v20161208",
"org.eclipse.jetty:jetty-webapp:9.4.0.v20161208",
"org.eclipse.jetty:jetty-xml:9.4.0.v20161208"



Also can someone please suggest how above can be fixed?
Thanks in advance.




1 Answer
1



It's not possible with JAXB, but if you want to switch over to using Jackson for XML, then you easily achieve this. All you need to do is annotate the Map getter in your bean with @JsonAnyGetter. This will cause Jackson to serialize the Map keys to be normal elements in the XML. Here's a test


Map


@JsonAnyGetter


@Path("jackson-xml")
public class JacksonXmlResource

@GET
@Produces("application/xml")
public Response get()
Model model = new Model();
model.setProp("foo", "bar");
model.setProp("name", "Paul");
return Response.ok(model).build();



public static class Model
private Map<String, Object> props = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> getProps()
return this.props;


@JsonAnySetter
public void setProp(String key, Object value)
this.props.put(key, value);





This will result in the following XML


<Model>
<foo>bar</foo>
<name>Paul</name>
</Model>



To use Jackson for XML, you need to do the following:



Add the Jackson dependency and remove (exclude) the JAXB provider


<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-jaxb</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-xml-provider</artifactId>
<version>$jackson2.version</version>
</dependency>



You should exclude the JAXB provider from the jersey-container-servlet dependency that you should already have in your project. The $jackson2.version should be whatever version of Jackson you are currently using in your project. If you don't have any explicit dependencies on Jackson, but are using jersey-media-json-jackson, find out what version of Jackson that pulls in. You want to make sure you don't have any conflicting versions.


jersey-container-servlet


$jackson2.version


jersey-media-json-jackson



You need to register the JacksonJaxbXMLProvider with Jersey


JacksonJaxbXMLProvider


public AppConfig extends ResourceConfig
public AppConfig()
register(JacksonJaxbXMLProvider,class);




With these two things done, it should work.



The cool thing about using Jackson for XML, is that the same Jackson annotations you use for your JSON, can also be used for XML, like the @JsonProperty. Also Jackson understands JAXB annotations (most of them). So you can probably just leave your JAXB annotations the same when you are migrating from JAXB to Jackson. As far as prop order, it will not work with dynamic properties, only for the ones that are already defined.


@JsonProperty


ContextResolver<XmlMapper>



If you can't add any new dependencies to your project, then another way to accomplish this is to just dynamically create the XML using the org.w3c.dom APIs, which are standard Java classes. It will be much more verbose and more more work, but it will get you what you want. See this post for some explanation. Here is an example. It should be easy to follow with the added comments.


org.w3c.dom


import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;


@Path("dom-api")
public class DomXmlResource

@GET
@Produces("application/xml")
public Response getXml() throws Exception
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

Document doc = docBuilder.newDocument();

// create root element
Element rootEl = doc.createElement("Model");
doc.appendChild(rootEl);

Model model = new Model();
model.setProp("foo", "bar");
model.setProp("name", "Paul");
model.setValue("FooBar");

// set static defined properties
Element valueEl = doc.createElement("value");
valueEl.appendChild(doc.createTextNode(model.getValue()));
rootEl.appendChild(valueEl);

// set dynamic properties
for (Map.Entry<String, Object> entry: model.getProps().entrySet())
Element dynamicEl = doc.createElement(entry.getKey());
dynamicEl.appendChild(doc.createTextNode(String.valueOf(entry.getValue())));
rootEl.appendChild(dynamicEl);


// return StreamingOutput so we can just stream the
// XML results without having to store the String into memory.
StreamingOutput entity = new StreamingOutput()
@Override
public void write(OutputStream out)
throws IOException, WebApplicationException
try
// write the XML structure to the output stream.
Transformer transformer = TransformerFactory.newInstance()
.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StreamResult result = new StreamResult(out);
DOMSource source = new DOMSource(doc);
transformer.transform(source, result);
out.flush();
catch (Exception e)
e.printStackTrace();


;

return Response.ok(entity).build();


public static class Model
private String value;
private Map<String, Object> props = new HashMap<>();

public String getValue()
return this.value;


public void setValue(String value)
this.value = value;


public Map<String, Object> getProps()
return this.props;


public void setProp(String key, Object value)
this.props.put(key, value);







Thanks so much for wonderful explanation.I did the changes and added the below in the class When i add the prop to below class When i add the prop to below class @XmlRootElement(name = "customer_info") public class Customer private String name;private Integer id;private long time;private Map<String, Object> map = new TreeMap<>(); @JsonAnyGetter public Map<String, Object> getMap() return map;@JsonAnySetterpublic void setMap(String name, Object values) this.map.put(name, values); but i dot see the new tags in the response.can you pls let me know what i am missing here.
– user3062513
Aug 14 at 10:48





Can you post all your dependencies and the all the application configuration code. It's hard to tell what's wrong without seeing it. There are many ways to make this work, so I need to see the whole picture of what you are doing to see if you are doing it right. Please edit your post with this information and also post the code you just added in the comment. It's not suitable for a comment.
– Paul Samsotha
Aug 14 at 17:15






I have updated the post.can u pls help me how to fix this?
– user3062513
Aug 17 at 22:37





I need to see all your dependencies and your application configuration. Without these things, I cannot see the whole picture and we will have to keep going back and forth. Please post exactly what I ask for. Thanks.
– Paul Samsotha
Aug 17 at 22:48






I have added the config and all dependencies.Pls let me know exactly what other info is required.
– user3062513
Aug 18 at 20:43






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Help:Category

How can temperature be calculated given relative humidity and dew point?

I have a recursive function to validate tree graph and need a return condition