If we develop REST using Spring MVC, it will support XML and JSON data. I have wrote ContentNegotiationViewResorver in my spring config bean app-servlet.xml
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
p:order="1">
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml" />
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller">
<bean class="org.springframework.oxm.xstream.XStreamMarshaller"
p:autodetectAnnotations="true" />
</property>
</bean>
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</list>
</property>
</bean>
And my spring REST Controller is:
#Controller
#RequestMapping("/rest/customers")
class CustomerRestController {
protected Log log = LogFactory.getLog(CustomerRestController.class);
#RequestMapping(method = POST)
#ResponseStatus(CREATED)
public void createCustomer(#RequestBody Customer customer,
HttpServletResponse response) {
log.info(">>>" + customer.getName());
response.setHeader("Location", String.format("/rest/customers/%s",
customer.getNumber()));
}
#RequestMapping(value = "/{id}", method = GET)
#ResponseBody
public Customer showCustomer(#PathVariable String id) {
Customer c = new Customer("0001", "teddy", "bean");
return c;
}
#RequestMapping(value = "/{id}", method = PUT)
#ResponseStatus(OK)
public void updateCustomer(#RequestBody Customer customer) {
log.info("customer: " + customer.getName());
}
I set #XStreamAlias("customer") annotation in my customer domain class.
But when I try access http://localhost:8080/rest/customers/teddy.xml it always response JSON data.
I set #XmlRootElement(name="customer") annotation in my customer domain class.
But when I try access http://localhost:8080/rest/customers/teddy.json it always response XML data.
Is there some thing wrong ?
I think "xml" content type should be mapped to "text/xml" not to "application/xml". Also, to force content type resolvers based on extension, you can try to set the "favorPathExtension" property of "ContentNegotiatingViewResolver" to true(though it should have been true by default!)
EDIT: I have now added a working sample at this GIT location - git://github.com/bijukunjummen/mvc-samples.git, if you bring up the endpoint, using mvn tomcat:run, the json is served at http://localhost:8080/mvc-samples/rest/customers/teddy.json and xml at http://localhost:8080/mvc-samples/rest/customers/teddy.xml. This uses JAXB2 not XStream, as I am familiar with JAXB. One thing I noticed was that when my JAXB annotations were not correct in Customer class, Spring was serving out JSON and not XML the way you saw it(You can replicate it by removing the XMLRootElement annotation from Customer class), once I fixed up my annotations, I got back XML as expected. So it could be that there is something wrong with your XStream configuration.
EDIT 2: You are right!! I did not notice, once I got back xml, I assumed that json is working now. I see the problem, in AnnotationMethodHandlerAdapter, the handling for #ResponseBody is a little strange, it completely ignores the ViewResolvers, and uses the registered MessageConverters instead completely bypassing the ContentNegotiatingViewResolver, one workaround for now is to use #ModelAttribute annotation for response, instead of #ResponseBody, this way the view Resolvers are getting called. Try now using the project at git#github.com:bijukunjummen/mvc-samples.git and see if it works for you. This could be a Spring bug, you may try and bring it up in the Spring forum and see what they recommend.
What Accept headers are sent to your server?
Make sure the content type you would like to request is in this list.
Spring 3.1 solves the problem you mention using the new produces element on the #RequestMapping annotation. This allows you to control the HttpMessageConverter that Spring applies to your object.
I wrote a blog post about it:
http://springinpractice.com/2012/02/22/supporting-xml-and-json-web-service-endpoints-in-spring-3-1-using-responsebody/
I had the same problem. I assume you're using Spring 3 and you've used <mvc:annotation-driven/>. I'm not entirely sure, but I think this creates some conflict based on the message converters that the mvc namespace configures.
Using the oxm namespace worked for me:
#XmlRootElement(name="person")
class Person {
private String firstName;
private String lastName;
}
#Controller
#RequestMapping("person")
class PersonController {
#RequestMapping("list")
public #ResponseBody Person getPerson() {
Person p = new Person();
p.setFirstName("hello");
p.setLastName("world");
return p;
}
}
Content Configuration (mvc and internal view resolver are in another context):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<oxm:jaxb2-marshaller id="jaxbMarshaller">
<oxm:class-to-be-bound name="package.Person" />
</oxm:jaxb2-marshaller>
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="defaultContentType" value="text/html" />
<property name="ignoreAcceptHeader" value="true" />
<property name="favorPathExtension" value="true" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller" ref="jaxbMarshaller" />
</bean>
</list>
</property>
</bean>
</beans>
This example uses JAXB, so you'd need jaxb-api and jaxb-impl on the classpath.
Also, just a tip, you don't need the app-servlet.xml. In your web.xml, set the config to null and let the Context Listener load them for you:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/mvc-context.xml, /WEB-INF/spring/content-negotiation-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value/>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Well I got a solution but I don't know if it's the right way in your method show customer:
#RequestMapping(value = "/{id}", method = GET)
#ResponseBody
public Customer showCustomer(#PathVariable String id) {
Customer c = new Customer("0001", "teddy", "bean");
return c;
}
In this part, we are using MVC of spring and in the controller we should be return a view, so I removed the annotation #ResponseBody and I return a String with the name of the view because in our XML we added a ContentNegotiatingViewResolver and when we have ResponseBody the contentnegociationviewresolver is ignored because is waiting for a view but we returned the object so the method should be like that:
#RequestMapping(value = "/{id}", method = GET)
public String showCustomer(#PathVariable String id, ModelMap model) {
Customer c = new Customer("0001", "teddy", "bean");
model.addAttribute("customer",c);
return "myView";
}
well that works for me, if you have problems you can add to your app-servlet.xml
this bean, but I don't think that you have to add this.
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/views/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
I got the answers from mkyong.com
Accessing the controller using a browswer will send a typical browser Accept header. It will not match any view resolver and default to the first one (application/xml) or it matches because application/xml is in the Accept list.
I can recommend using RestClient http://code.google.com/p/rest-client/ to have complete control over what Accept header (if one at all) you want to send.
I don't recommend using text/xml as the default character set is US-ASCII and not UTF-8. This might create funky encoding problems down the road. You can always specify the encoding but appliation/xml has a UTF-8 default encoding.
Related
I'm having a problem with RESTlet Framework configuration with Spring. I want to have one global filter for all requests and responses. I guess I can use Filter class and it's methods beforeHandle, afterHandle and setNext like this:
Filter.beforeHandle() -> Router -> Filter.afterHandle()
The problem is, that I'm using Spring configured RESTlet and I don't know if the regular Filter will work correctly with SpringRouter from org.restlet.ext.spring package. My current restlet configuration is as follows:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="root" class="org.restlet.ext.spring.SpringRouter">
<property name="attachments">
<map>
<entry key="/login">
<bean class="org.restlet.ext.spring.SpringFinder">
<lookup-method name="create"
bean="loginResource" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>
I'm thinking about adding a bean with id root and class that extends class Filter, and pass to it as property next a bean with id router (which currently is called root). What do you think about this solution?
Mixing Restlet classes from the Spring extension with other one shouldn't be an issue. Spring* classes only provide additional configurability.
I have used this code as the basis of my development so far : Ajax Simplifications from springsource.
Here is the html & jquery/javascript code:
<c:url var="Controller" value="/ControllerUrl" />
...
var previewDialog = $("<div></div>").dialog({
//all the dialog setttings
});
$(".opener").click(function() {
previewDialog.load("${Controller}",function(data) {
previewDialog.dialog('open');
});
return false;
});
And the controller :
#RequestMapping(value = "/ControllerUrl", method = RequestMethod.GET)
public String previewDialog(Model model) {
MyClass myClass = new MyClass();
myClass.setTitle("SUCCESS");
model.addAttribute(myClass);
return "dialogContent";
}
This is all almost working, except in dialogContent.jsp (which is indeed opened in my dialog) "SUCCESS" is not printed:
<div id="divContent">
Title : ${myClass.title} <br>
</div>
What am I missing/doing wrong ?
Secondly, what is the besy way to submit json data to server in this context - I attempted using $.ajax() and $.postJSON() but ran into problems as they work differently to the $.load() statement.
Thanks in advance.
Can you try:
In the Controller:
return new ModelAndView("view-name", "myclass", myClass);
In your JSP:
${myClass.title}
The trouble is that this would till return HTML rather than just plain text.
Also, you can probably return json or xml by changing your controller as follows:
#RequestMapping(value = "/ControllerURL", method = RequestMethod.GET, headers="Accept=application/xml, application/json")
public #ResponseBody DealManager homeXmlJson(Locale locale, Model model) {
MyClass myClass = new MyClass();
myClass.setTitle("SUCCESS");
return myClass;
}
Then when you call it using $.getJSON it should return a json representation of the object from which you should be able to extract the title.
Use a tool like REST-Client to see what is returned when you pass different Accept parameters to the controller URL. the parameter being:
Accept: text/html, Accept: application/json, Accept: application/xml
You will have to configure your rest context as well. Here is an example of one I'm using:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonConverter" />
<ref bean="marshallingConverter" />
<!-- <ref bean="atomConverter" /> -->
</list>
</property>
</bean>
<!-- Handle JSON Conversions -->
<bean id="jsonConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>
<!-- Handle XML Conversion -->
<bean id="marshallingConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml" />
</bean>
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.pack1.app.domain.MyEntity</value>
<value>com.pack1.app.service.MyEntityTwo</value>
</list>
</property>
</bean>
</beans>
I have an application with Spring 3.0.5.RELEASE trying to get the full content of a post using #RequestBody. The method is called, but the string passed is always empty. I have checked, by placing breakpoints, that the StringHttpMessageConverter is called, but the inner HttpInputMessage is empty.
I've seen this issue with both Jetty and Tomcat, so I'm discarding it's a problem with the container.
Here is my sample controller:
#Controller
#RequestMapping("/")
public class SubscriptionController {
#RequestMapping(value = "/requestbody", method = RequestMethod.POST)
public ModelAndView mycustomAction(#RequestBody String body) {
// body is always empty
Logger.getLogger(this.getClass()).debug("REQUEST BODY '" + body + "'");
return new ModelAndView("empty");
}
}
My application context is defined as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Enable auto detection of controllers -->
<context:component-scan base-package="com.big.viajerotelcel.controller" />
<!--
use annotation driven mvc and one single validator with JSR-303
standard
-->
<mvc:annotation-driven />
<!--
Message source for this context, loaded from localized "messages_xx"
files
-->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames" value="classpath:i18n/messages" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
<!-- Declare the Interceptor -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"
p:paramName="locale" />
</mvc:interceptors>
<!-- Declare the Resolver -->
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
<!-- will load Tiles definitions! -->
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/general.xml</value>
</list>
</property>
</bean>
<!-- Tiles view resolver -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.tiles2.TilesView" />
</bean>
<!-- Configure the multipart resolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--
one of the properties available; the maximum file size in bytes (5MB)
-->
<property name="maxUploadSize" value="5120000" />
</bean>
<!-- Adding these lines has no effect, the StringHttpMessageConverter is called either way -->
<!-- <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/>-->
<!-- -->
<!-- <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">-->
<!-- <property name="messageConverters">-->
<!-- <list>-->
<!-- <ref bean="stringHttpMessageConverter"/>-->
<!-- </list>-->
<!-- </property>-->
<!-- </bean>-->
</beans>
I'm testing this using curl as follows:
curl -d asd=123 -d qwe=456 http://localhost:8080/requestbody
Any ideas or help is more than welcomed!
Here is a code snippet of ServletServerHttpRequest, which extends HttpInputMessage. I am pretty positive this is the implementation that you are using in your code:
public InputStream getBody() throws IOException {
return this.servletRequest.getInputStream();
}
In other words, the request body is meant to be read as the input stream of the HttpServletRequest object.
The request's input stream is not valid in several situations, but I can't find the correct documentation for it at the moment. For example, if you call request.getParameter() on a post request, tomcat has to read the input stream in order to interpret the parameters, thus afterwards when you read the input stream, it is empty because it has reached the end already.
Perhaps you are invoking getParameter somewhere in an interceptor or perhaps a filter defined in web.xml. Another option is that Spring is doing that for you, for example, if your controller has some other method with complex #RequestMappings (such as reading param values, or header values).
I have two suggestions for you:
Add a servlet filter (before spring gets a chance to act), and wrap the request with your own wrapper (just extend HttpServletRequestWrapper). This way you can put breakpoints or log messages at some methods of the request object and see who's calling them.
Use a pojo object parameter, and setup the bindings. It seems like a much cleaner way to read post data.
How are you POSTing messages to this URL? Are you positive that the HTTP request contains what you think it does? I suggest removing any web browsers from the picture and drop down to something low-level like curl which lets you send any type of HTTP message yourself.
Had a similar problem - the string received by spring controller was always empty. Tinkered with my spring config but with no result. Finally the problem was that the client was actually was not sending anything body!(due to some typo of mine)
If found with a similar error, its worth checking once if the client's payload is actually non-empty.
Another reason that your XML may not be getting marshalled into your JAXB object is related to the namespaces in the XML.
Versions of java after 1.8.101 are more strict about parsing namespaced XML.
See JAXB doesn't unmarshall after updating java from 1.8.0_77 to 1.8.0_121
In my case I was seeing a request body with all nulls and no exception being thrown to indicate that the XML parsing had failed.
I'm trying to return content other than json in my Controller, but I can't seem to get it to work. Ideally, I'd like to return a rendered velocity template as plain text or html.
This is what I have in my controller:
#RequestMapping( value = "time", headers = "Accept=*/*", method = RequestMethod.GET )
public #ResponseBody
Date getCurrentTime( HttpServletRequest request, HttpServletResponse response ) {
response.setContentType( "text/plain" );
return new Date();
}
And this is in my springmvc-servlet.xml (I know this is not right...but I'm a bit lost here):
<context:component-scan base-package="com.paml.alerter.controller" />
<!-- Configures the #Controller programming model -->
<mvc:annotation-driven />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".vm" />
</bean>
<!-- This bean sets up the Velocity environment for us based on a root path
for templates. Optionally, a properties file can be specified for more control
over the Velocity environment, but the defaults are pretty sane for file
based template loading. -->
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/velocity/" />
</bean>
Does anyone know how to set this up right?
TIA
If your method is annotated with #ResponseBody, then the Spring MVC view layer will be bypassed entirely.
If you're not interested in JSON output, then #ResponseBody is inappropriate - just remove it, and your Velocity views will be used.
If you need to switch between JSON and some other View layer, then you should consider removing #ResponseBody and using ContentNegotiatingViewResolver instead. See the Spring docs for how to set this up.
Probably missing something completely obvious here, but here goes. I'm starting out with Spring MVC. I have a form controller to process inbound requests to /share/edit.html. When I hit this url from my browser, I get the following error:
The requested resource (/inbox/share/share/edit) is not available.
Here is my applicationContext-mvc.xml:
<bean id="publicUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" >
<property name="mappings" >
<value>
/share/edit.html=shareFormController
/share/list.html=shareController
/share/view.html=shareController
/folders.json=foldersController
/studies.json=studiesController
</value>
</property>
</bean>
<bean id="internalPathMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver" />
<bean id="shareFormController" class="com.lifeimage.lila.controller.ShareFormController" />
<bean id="shareController" class="com.lifeimage.lila.controller.ShareController" >
<property name="methodNameResolver" ref="internalPathMethodNameResolver" />
</bean>
and my form Controller:
public class ShareFormController extends SimpleFormController {
public ShareFormController() {
setCommandClass( Share.class );
}
#Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
//controller impl...
}
}
You should look at your view resolver. Make sure that it is resolving the logical name in your controller as you think it should. Looks like the name it is resolving it to does not exist currently
I think I've resolved this issue. There were two problems:
1) Implementations of SimpleFormController require a form and success view; which I had not configured here. As this is a server method for an AJAX client, I added a Spring-JSON view as follows:
<?xml version="1.0" encoding="UTF-8"?>
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
default-lazy-init="false" default-autowire="no"
default-dependency-check="none">
<bean name="jsonView" class="org.springframework.web.servlet.view.json.JsonView">
<property name="jsonErrors">
<list>
<ref bean="statusError" />
<ref bean="modelflagError" />
</list>
</property>
</bean>
<bean name="statusError"
class="org.springframework.web.servlet.view.json.error.HttpStatusError">
<property name="errorCode"><value>311</value></property>
</bean>
<bean name="modelflagError"
class="org.springframework.web.servlet.view.json.error.ModelFlagError">
<property name="name"><value>failure</value></property>
<property name="value"><value>true</value></property>
</bean>
which can be used for all controllers that return JSON.
2) I switched from a SimpleURLHandlerMapping to ControllerClassNameHandlerMapping and relied on Spring naming conventions ( controllerClassName/method.html ), which fixed the routing issue. Might not be a long term solution, but got me through the task.
Did you check your log output? Spring MVC is generally pretty verbose in what it outputs.
Also, the URL you've posted (/inbox/share/share/edit) does not seem to match what you are configuring (/share/edit.html).
#jordan002 when I see all the hoops you had to jump to accomplish your task, I feel obliged to share a very powerful Java MVC framework that requires much less configuration. The framework is called Induction, check out the article Induction vs. Spring MVC, http://www.inductionframework.org/induction-vs-spring-mvc.html