Add JSON message converter for multipart/form-data - spring

In my Spring MVC server I want to receive a multipart/form-data request containing both a file (an image) and some JSON metadata.
I can build a well-formed multipart request where the JSON section has Content-Type=application/json.
The Spring service is in the form:
#RequestMapping(value = MY_URL, method=RequestMethod.POST, headers="Content-Type=multipart/form-data")
public void myMethod(#RequestParam("image") MultipartFile file, #RequestParam("json") MyClass myClass) {
...
}
The file is correctly uploaded, but I'm having problems with the JSON part. I get this error:
org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'myPackage.MyClass'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [myPackage.MyClass]: no matching editors or conversion strategy found
If I don't use multipart request JSON conversion works well using Jackson 2, but when using multipart I get the previous error. I think I have to configure the multipart message converter to support JSON as part of the message, but I don't know how. Here is my configuration:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
All works well if I use String as type of myClass instead of MyClass, but I want to use the Spring MVC support for parameter conversion.

If you use the #RequestPart annotation instead of #RequestParam, it will actually pass the parameters through the message converters.
So, if you change your controller method to the following, it should work as you describe:
#RequestMapping(value = MY_URL, method=RequestMethod.POST, headers="Content-Type=multipart/form-data")
public void myMethod(#RequestParam("image") MultipartFile file, #RequestPart("json") MyClass myClass) {
...
}
You can read more about it in the Spring reference guide: http://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/html/mvc.html#mvc-multipart-forms-non-browsers

i don't have any idea how do it this but i know #RequestParam("json") MyClass myClass u can change to #RequestParam("json") String myClass and build object class by JSON converted! It's not good but it's works

Related

MultipartFile Wrapper Object woks as #ModelAttribute but not as #RequestParam

I created a spring RestController to process fileUpload from a react js UI.
To be able to use custom validations by #Validated annotation, had to wrap the MultipartFile into a class, I named it as UploadedFile. Created my Post Handler method with argument as #ModelAttribute.
Everything works fine.
validate(target, error) method in my custom validator is called and inside POST handler method, UploadedFile object has the multipart file containing the uploaded document..
Here is a perfectly working code
#PostMapping("/file")
public ResponseEntity<?> receiveFile(#Validated #ModelAttribute UploadedFile file) {
}
#Getter
#Setter
public class UploadedFile {
MultipartFile file;
}
// one CustomValidator class and webDataBinder.addValidators(customValidator) in controller
multipart.enabled=true //in application.properties
So far everything works as expected, Problem arise when
Someone asked me that, #ModelAttribute is a spring MVC construct, as this is a microservice, which in future, apart from my React UI, will cater to other api requests too, so I should use #RequestParam instead of #ModelAttribute.
So I changed #ModelAttribute to #RequestParam as follows
#PostMapping("/file")
public ResponseEntity<?> receiveFile(#Validated #RequestParam(name = "file") UploadedFile file)
But now I get below exception
Failed to convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'com.x.x.x.UploadedFile';
nested exception is java.lang.IllegalStateException:
Cannot convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'com.x.x.x.UploadedFile':
no matching editors or conversion strategy found
If, for #RequestParam, I change type as MultipartFile instead of UploadedFile then my posthandler gets called but the custom Validator doesn't gets called by spring before handler call,
I tried to use instanceOf MultiPartFile in validator supports method by still no avail.
#PostMapping(value="/file" )
public ResponseEntity<?> receiveFile(#Validated #RequestParam(name = "file") MultipartFile file)
I've the work around to explicitly call the custom validator method by code as first line in POST handler, so I dont need a solution MY QUESTION IS
How come, without adding any custom #Bean or any extra external dependencies everything works fine with #ModelAttribute, but merely changing the annotation #RequestParam doesn't work.

Get raw json string in Spring MVC Rest

Im have #RestController and this method. how can I get any json and then select the handler depending on the method and pass it there for processing
PS. I use GSON instead of JACKSON
You can use #RequestBody in your method and take a String parameter:
public AbstractJsonResponse(#PaqthVariable String method, #RequestBody String json) {
...
}
See here: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html
Mainly the part that says:
Annotation indicating a method parameter should be bound to the body of the web request

cxf out interceptor - reference to response pojo object

I need to get the response Java object that is returned from my service for some processing of the data. I don't want to write the code to process this data in the ServiceImpl class itself since I want to keep it configuration based. I have written the out interceptor.
As per answer to this question , the POJO object should be available in the out interceptor, however I see that the object is actually an intermediate class of response. I get a ClassCastException with the code mentioned in above link.
Am I missing something? Can the same POJO object returned by Service class be available in the Out interceptor?
Any other approach to accomplish this is also welcome.
MyOutInterceptor.java:
public class MyOutInterceptor extends AbstractPhaseInterceptor<Message> {
public MyOutInterceptor() {
super(Phase.MARSHAL); // Tried Phase.PRE_LOGICAL as well
}
public void handleMessage(Message message) throws Fault {
MessageContentsList objs = MessageContentsList.getContentsList(message);
if (objs != null && objs.size() == 1) {
Object responseObj = objs.get(0);
MyData data = (MyData) responseObj; // fails here with ClassCastException
...
}
applicationContext.xml
<bean class="com.xyz.interceptor.MyOutInterceptor" id="outInterceptor" />
<jaxws:endpoint id="dataService" implementor="#masterDataService" address="/MasterDataService">
...
<jaxws:outInterceptors>
<ref bean="outInterceptor" />
</jaxws:outInterceptors>
</jaxws:endpoint>
Pre-logical phase will work, but you would need to do an:
addBefore(WrapperClassOutInterceptor.class.getName());
to make sure it's run before that interceptor.

#RequestBody or #ModelAttribute with Spring+REST web services

I am creating a Restful website and Web services for iPhone and android apps with Spring 3.1. In my application, i am using Spring Message Convertors (org.springframework.http.converter.json.MappingJacksonHttpMessageConverter) to converting JSON into Java object and vice-versa.
My objective is that there should be only single controller
method(same URL) that should be used by JSP page, Iphone/Andois app.
I am using Spring form tag for object binding from JSP to controller with the help of #ModelAttribute like below.
#RequestMapping(value = "reset-password", method = RequestMethod.POST)
public ModelAndView resetPassword(#ModelAttributeForgot forgotPassword,
HttpServletRequest request) {
System.out.println("data recived=="+forgotPassword.getNewPassword());
}
But the same is NOT working in the case if the data is being posted from iPhone/Android app and the result is:
data recived==null;
So to overcome this problem i have used #RequestBody annotation at place of #ModelAttribute.
So my controller looks like below:
#RequestMapping(value = "reset-password", method = RequestMethod.POST)
public ModelAndView resetPassword(#RequestBody Forgot forgotPassword,
HttpServletRequest request) {
System.out.println("data recived=="+forgotPassword.getNewPassword());
}
It works then and the result i got is:
data recived==somedata;
But #RequestBody then doesn't work with spring form on JSP page and the data doesn't get converted into object and i got null values.
Can't i use #RequestBody annotation to post data in form of JSON
with spring form tag from JSP page??
Is there any way by using which i can post data from my JSP form as
well as from I phone App by using only a single controller method(either #ModelAttribute or #RequestBody).
EDIT:
While writing String in place of Bean class, i am able to get the content in form of plain text, as below:
#RequestMapping(value = "reset-password", method = RequestMethod.POST)
public ModelAndView resetPassword(#RequestBody String string,
HttpServletRequest request) { }
Result from web page call:
uid=11&confirmPassword=somepassword&newPassword=somepassword
Result from iPhone using web service call(in **JSON)**
{"newPassword":"somepassword","confirmPassword":"somepassword","uid":"11"}
But problem is that using this approach i have to parse the JSON string into Java object manually. And in web page content i have to find the values manually that i don't want.
Please help.
Regards,
Arun Kumar
Sorry, but I don't believe there is a way, because #ModelAttribute is bound from form post parameters and #RequestBody passes the body straight to the Json converter. You could replace the spring form tag with a simple json post, but that is probably less convenient than having two #RequestMapping methods.
Its #RequestBody. I feel its better to specify the mime type that you are expecting and producing as output using #RequestMapping as,
#RequestMapping(value="/authenticate",produces="application/json",
consumes="application/json",method=RequestMethod.POST)
Then register appropriate message converters with AnnotationMethodHandlerAdapter
This message converter is responsible for Marshalling & unmarshalling of your request & response entity based on produces & consumes attributes.
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
<property name="order" value="1" />
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" >
<property name="supportedMediaTypes" value="application/json"/>
</bean>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
</bean>
</list>
</property>
</bean>

Spring MVC 3: Is consumes supposed to disambiguate request mappings?

I have two request mappings in a Spring MVC 3 application, one which takes json and xml, and another that takes application/x-www-form-urlencoded data. Example:
#RequestMapping(value={"/v1/foos"}, method = RequestMethod.POST, consumes={"application/json", "application/xml"})
public FooDTO createFoo(#RequestBody FooDTO requestDTO) throws Exception {
...
}
#RequestMapping(value={"/v1/foos"}, method = RequestMethod.POST, consumes="application/x-www-form-urlencoded")
public FooDTO createFooWithForm(#ModelAttribute FooDTO requestDTO) throws Exception {
...
}
I expected that the different consumes parameter makes each request unique, though I get an java.lang.IllegalStateException: Ambiguous handler methods mapped....
Should consumes and produces makes requests unique? Any ideas?
Edit 1: To add weight to this, if you set the content-type in the header rather than using consumes, this actually works and makes them unique: headers="content-type=application/x-www-form-urlencoded. Perhaps there is a bug with consumes?
Edit 2: We're using Spring 3.1.1.RELEASE.
This has been resolved by Marten Deinum on the Spring Forum (here):
You should change both the HandlerMapping as well as the
HandlerAdapter (use the RequestMappingHandlerAdapter).
In theory it should work if it doesn't feel free to register an issue.
The solution to this problem was to use the correct HandlerMapping and HandlerAdapter in my servlet configuration:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
Thanks Marten.

Resources