Reputation: 3903
I need to write a rest service which accepts XML/JSON as a input (POST method) and XML/JSON as a output (based on the input format). I have tried a below approach to achieve this but doesn't helped out.Endpoint method accepts both XML/JSON but while responding it always gives either JSON or XML based on the order specified in @RequestMapping -produces.Any help will be really appreciated.
My endpoint method:
@RequestMapping(value = "/getxmljson", method = RequestMethod.POST,produces={"application/json","application/xml"},
consumes={"application/json", "application/xml"})
public @ResponseBody Student processXMLJsonRequest(@RequestBody Student student)
throws Exception {
System.out.println("*************Inside Controller");
return student;
}
POJO Class: Student.java
import java.io.Serializable;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@XmlRootElement(name = "student")
@XmlType(propOrder = {"id", "name", "graduationTime", "courses"})
@JsonPropertyOrder({"id", "name", "graduationTime", "courses"})
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String graduationTime;
private ArrayList<Course> courses = new ArrayList<Course>();
@XmlElement
public int getId() { return id; }
@XmlElement
public String getName() { return name; }
@XmlElement
public String getGraduationTime() { return graduationTime; }
@XmlElement
public ArrayList<Course> getCourses() { return courses; }
public void setId(int value) { this.id = value; }
public void setName(String value) { this.name = value; }
public void setGraduationTime(String value) { this.graduationTime = value; }
public void setCourses(ArrayList<Course> value) { this.courses = value; }
@JsonIgnore
public String toString() {
return this.name + " - "
+ graduationTime == null? "Unknown" : graduationTime.toString();
}
public Student() {}
public Student(int id, String name, String graduationTime) {
this.id = id;
this.name = name;
this.graduationTime = graduationTime;
}
}
POJO Class: Course.java
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@XmlRootElement(name = "course")
@XmlType(propOrder = {"courseName", "score"})
@JsonPropertyOrder({"courseName", "score"})
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
private String courseName;
private Integer score;
public @XmlElement String getCourseName() { return courseName; }
public @XmlElement Integer getScore() { return score; }
public void setCourseName(String value) { courseName = value; }
public void setScore(Integer value) { score = value; }
public Course() {}
public Course(String courseName, Integer score) {
this.courseName = courseName;
this.score = score;
}
}
spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/web-services
http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Configure to plugin JSON as request and response in method handler -->
<beans:bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<beans:property name="messageConverters">
<beans:list>
<beans:ref bean="jsonMessageConverter" />
<beans:ref bean="xmlMessageConverter" />
</beans:list>
</beans:property>
</beans:bean>
<!-- Configure bean to convert JSON to POJO and vice versa -->
<beans:bean id="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</beans:bean>
<beans:bean id="xmlMessageConverter"
class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter">
</beans:bean>
<beans:bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
</beans:bean>
<beans:bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
<context:component-scan base-package="com.test" />
</beans:beans>
Json Input:
{
"id":2014,
"name":"test",
"graduationtime":"09/05/2014",
"courses":[
{
"courseName":"Math",
"score":150
},
{
"courseName":"Che",
"score":150
}
]
}
XML Input:
<?xml version="1.0" encoding="UTF-8" ?>
<student>
<id>2014</id>
<name>test</name>
<graduationTime>09/05/2014</graduationTime>
<courses>
<courseName>Math</courseName>
<score>150</score>
</courses>
<courses>
<courseName>Che</courseName>
<score>150</score>
</courses>
</student>
Upvotes: 23
Views: 83895
Reputation: 107
If the resource define as below
@GET
@Path("/{id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Student getStudent(@PathParam("id") String id) {
return student(); // logic to retunrs student object
}
Then the request should contains 'accept' header ('application/json' or application/xml'),
then it returns response in json or xml format.
Sample request :
curl -k -X GET -H "accept: application/json" "https://172.17.0.5:8243/service/1.0/222"
Sample Student class
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "student")
public class Student {
private int id;
private String name;
private String collegeName;
private int age;
@XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElement
public String getCollegeName() {
return collegeName;
}
public void setCollegeName(String collegeName) {
this.collegeName = collegeName;
}
public int getAge() {
return age;
}
@XmlElement
public void setAge(int age) {
this.age = age;
}
}
Upvotes: 0
Reputation: 1532
i was facing the same problem like yours. Below is my solution and sample.
Below is maven dependency that you need to include:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.4.3</version>
</dependency>
dispatcher-servlet.xml
<mvc:annotation-driven
content-negotiation-manager="contentManager" />
<bean id="contentManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="ignoreAcceptHeader" value="false" />
<property name="defaultContentType" value="application/json" />
<property name="useJaf" value="false" />
</bean>
and my @RequestMapping ( you can use your own request mapping )
@RequestMapping(value = "/testXMLJSON",
method = RequestMethod.GET, produces = {
MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE })
@ResponseBody
public ArtworkContentMessageType testXMLJSON()
{
//this is GS1 xml standard mapping.
ArtworkContentMessageType resp = new ArtworkContentMessageType();
StandardBusinessDocumentHeader standarBusinessDocumentHeader = new StandardBusinessDocumentHeader();
resp.setStandardBusinessDocumentHeader(standarBusinessDocumentHeader );
ArtworkContentType artWorkContent = new ArtworkContentType();
resp.getArtworkContent().add(artWorkContent);
return resp ;
}
If application/xml
is required then, below headers must be present
Content-Type:application/xml
Accept:application/xml
Upvotes: 0
Reputation: 1055
Adding to Manish's answer above, if you don't wanna use xml based configuration use this java based configuration instead-
@Bean
public ViewResolver contentNegotiatingViewResolver() {
ContentNegotiatingViewResolver resolver =
new ContentNegotiatingViewResolver();
List<View> views = new ArrayList<>();
views.add(new MappingJackson2XmlView());
views.add(new MappingJackson2JsonView());
resolver.setDefaultViews(views);
return resolver;
}
Upvotes: 15
Reputation: 20135
The best practice for handling different data formats with the same controller is to let the framework do all the work of figuring out the marshalling and unmarshalling mechanisms.
Step 1: Use minimal controller configuration
@RequestMapping(value = "/getxmljson", method = RequestMethod.POST)
@ResponseBody
public Student processXMLJsonRequest(@RequestBody Student student) {
return student;
}
There is no need to specify consumes
and produces
here. As an example, consider that you may want this same method to handle other formats in the future such as Google Protocol Buffers, EDI, etc. Keeping the controllers free of consumes
and produces
will let you add data formats through global configuration instead of having to modify the controller code.
Step 2: Use
ContentNegotiatingViewResolver
instead ofRequestMappingHandlerAdapter
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</list>
</property>
</bean>
Let the view resolver decide how to read incoming data and how to write it back.
Step 3: Use
Accepts
andContent-Type
HTTP headers
Hitting your controller with the correct HTTP header values will force ContentNegotiatingViewResolver
to marshal and unmarshal data automatically using the appropriate data representations.
If you want to exchange data in JSON format, set both headers to application/json
. If you want XML instead, set both to application/xml
.
If you do not want to use HTTP headers (which ideally you should), you can simply add .json
or .xml
to the URL and ContentNegotiatingViewResolver
will do the rest.
You can check out my sample app that I created using your code snippets that works fine for JSON and XML.
Upvotes: 35
Reputation: 48213
Register a filter that intercepts each request, warp the HttpServletRequest
into an implementation of HttpServletRequestWrapper
and returns the Content-Type
value for Accept
header. For example, you can register a filter named SameInSameOutFilter
like following:
@Component
public class SameInSameOutFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
SameInSameOutRequest wrappedRequest = new SameInSameOutRequest((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
}
It wraps current request in a SameInSameOutRequest
:
public class SameInSameOutRequest extends HttpServletRequestWrapper {
public SameInSameOutRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
if (name.equalsIgnoreCase("accept")) {
return getContentType();
}
return super.getHeader(name);
}
}
This wrapper tells spring mvc to select a HttpMessageConverter
based on request's Content-Type
value. If request body's Content-Type
is application/xml
, then the response would be an XML
. Otherwise, the response would be JSON
.
The other solution is to manually set the Accept
header along with Content-Type
in each request and avoid all these hacks.
Upvotes: 0