Spring MVC 4 AJAX upload MultipartFile with extra parameters - java

I'm currently developing a file upload with AJAX (jQuery) and Spring MVC 4. Besides the file itself I have to send some extra parameters, such as a description of the file being uploaded. I'm using an $.ajax() call that sends a FormData object along with my CSRF token, like described below:
var formdata = new FormData();
formdata.append("description", $('#description').val());
formdata.append("file", $("#file")[0].files[0]);
$.ajax({
url: '/upload',
type: 'POST',
headers : {"X-CSRF-TOKEN" : $('#myToken').val()}
data: formdata,
enctype: 'multipart/form-data',
processData: false,
contentType: false,
success: function (data) {
alert("Data Uploaded: "+data);
}
});
I've found multiple examples of how to upload files using javascript's FormData object along with a Spring controller that receives a MultipartFile object, but when I try to retrieve the other parameters using #RequestParam I end up getting errors. Below is an example of what I was trying (which didn't work):
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFile(
#RequestParam(value = "file") MultipartFile file,
#RequestParam(value = "description") String description) {
//Do stuff...
}
After a lot of research and attempting different approaches, I found out that I could declare HttpServletRequest as a parameter and it would allow me to retrieve each parameter (but not the file itself). Below is an working example:
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFile(
#RequestParam(value = "file") MultipartFile file,
final HttpServletRequest request) {
String description = request.getParameter("description");
//Do some other stuff...
}
Even though the example above worked, the fact that I couldn't use annotations and have my parameters explicit in the method's signature was bugging me. So I tried a different approach changing the #PRequestParam annotation for a #ModelAttribute annotation, which surprisingly worked.
My questions are:
Why didn't #RequestParam worked, if the parameter was retrievable in the HttpServletRequest object?
Why did #ModelAttribute worked? Should I use it instead of retrieving things explicitly from HttpServletRequest?

this comes probably too late but you could pass the description as a parameter in the url:
url: '/upload?' + $.param({description: $('#description').val()});
don't forget the question mark!
and for the data you just pass the actual file:
data: $("#file")[0].files[0]
and your controller would look like this:
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFile(
#RequestParam(value = "file") MultipartFile file,
#RequestParam(value = "description") String description) {
//Do stuff...
}
hope that helps!

Related

Spring MVC test throwing 415 HttpMediaTypeNotSupportedException

this may or may not be a somewhat long post, but I'm going to be pasting every single piece of information relating to this issue and the method I am testing, from Controller class with method to the a.jax snippet. I have asked about 4-6 developers and no one can find out the reason why its giving me a 415 error instead of a 200, because it just seems like I am doing everything correct. I just need some fresh eyes and new perspectives, hopefully someone could help me solve this. I will be pasting the classes and the relevant pieces now, and then a couple comments after the snippets.
Controller class
#Controller
#RequestMapping(value = "/user")
public class Controller
{
#Autowired
private Service service;
public Controller() {
}
#RequestMapping(value = "/landing/{age}/{name}/{title}/{family}/{gender}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public #ResponseBody String update(#RequestBody HouseModel model, #PathVariable int age, #PathVariable String name, #PathVariable String title, #PathVariable String family, #PathVariable String gender)
{
String result = service.update(model, age, name, title, family, gender);
// this will just return the string "Success" if update works or "Failed" if query is
wrong or not found
return result;
}
Service Class
#Service
public class Service
{
#Autowired
Dao dao;
public Service() {
}
public String update(HouseModel model, int age, String name, String title, String family)
{
return dao.update(HouseModel model, int age, String name, String title, String family);
}
}
Dao class
#Repository
public class Dao
{
public Dao () {
}
public String update(HouseModel model, int age, String name, String title, String family)
{
String result = "";
//some long query that updates the table and will populate result as "Success" or "Failed"
return result
}
}
Controller test class
#EnableWebMvc
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:applicationContext-testing.xml",
"classpath:applicationContext-EIPW.xml"})
public class ControllerTest {
#Autowired
private Controller controller;
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void test_update() throws Exception {
String URI = "/user/landing/22/Test/Mr/Yes/Male";
String content = "{\n \"HouseId\": 5,\n \"DateOfPurchase\": \"2019-01-01\",\n \"Price\": 100,\n \"Floors\": 5,\n \"Style\": \"Victorian\",\n}";
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.put(URI).contentType(MediaType.APPLICATION_JSON).content(content).accept(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andDo(MockMvcResultHandlers.print()).andReturn();
}
j.ajax
$j.ajax({
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
type: "PUT",
async: false,
data: JSON.stringify(
buildEdit(editRecords[i], ecRecord)
),
url:
"/user/landing/22/Test/Mr/Yes/Male",
dataType: "text"
printed error message
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /user/landing/22/Test/Mr/Yes/Male
Parameters = {}
Headers = {Content-Type=[application/json], Accept=[application/json]}
Handler:
Type = controller.Controller
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 415
Error message = null
Headers = {Accept=[application/octet-stream, */*, text/plain;charset=ISO-8859-1, */*, application/xml, text/xml, application/*+xml, application/x-www-form-urlencoded, multipart/form-data]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Some Comments:
So I've had over 50 stack overflow tabs open relating to the same problem, and a lot of them had similar solutions that seemed so simple. Here are some, if not all of the things I did in attempts to try and solve this problem:
Switched around the content-type and accepts headers of requestBuilder to be MediaTypes of APPLICATION_JSON or APPLICATION_JSON_VALUE or ALL_VALUE, etc
Added produces or consumes = "application/json" or MediaType.APPLICATION_JSON/APPLICATION_JSON_VALUE/ALL_VALUE into the requestMapping().
Played around with a.jax to change content-type or accepts around
A couple of other things that I don't remember, but alas the 415 status is still here
I also do have setters and a default constructor in the HouseModel, and default constructors in every layer. I am 99.9% sure I have tried almost everything, if not everything, unless I am just missing something and am being stupid then yeah. I sent the request with the body as JSON raw an as:
{
"HouseId": 5,
"DateOfPurchase": "2019-01-01",
"Price": 100,
"Floors": 5,
"Style": "Victorian",
}
and it returned back success, I will attach its headers here:
[![Picture Link][1]][1]
[1]: https://i.stack.imgur.com/AqKnY.png
There is something interesting though, I did get one method to work but it required no arguments in its parameters, it was just a get method (dao calls database to store stuff in a list):
**Controller method**
#RequestMapping(value = "/levels", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody String getLevels() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// there is a long logic after this map to populate the map
Map<LevelObject, List<LevelObject>> map = new HashMap<LevelObject, List<LevelObject>>();
return mapper.writeValueAsString(map);
}
This is such a simple test and it worked perfectly fine, giving me a status 200 and my expected result.
**Test class method**
#Test
public void test_getLevels() throws Exception {
String URI = "/user/levels";
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(URI).accept(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn();
}
**j.ajax for the getLevels method**
$j.ajax({
type: "POST",
url: "user/levels",
async: false,
dataType: "json",
Thank you so much!
Though I am not a front-end developer, I am sure that problem is in below line
data: JSON.stringify(
buildEdit(editRecords[i], ecRecord)
)
Reason - I tested your code locally. Code works fine from postman, if I select request body as raw and type as JSON
But if select request body as raw and type as "TXT". I am getting same error as you.
[![enter image description here][2]][2]
So, I am sure that your request body is not being built as JSON. rather it is being sent as some other format. Hence, the error.
[2]: https://i.stack.imgur.com/cqSCC.png
Also, you can try to change dataType: "text" to dataType: "json"
Please try the below,
$j.ajax({
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
type: "PUT",
async: false,
data:
buildEdit(editRecords[i], ecRecord),
url:
"/user/landing/22/Test/Mr/Yes/Male",
dataType: "json"
Remove converting the json to String
Change the dataType to json

Spring mvc - send xml text string to controller

I'm working on a java spring mvc application. I need to send an xml string to my controller, and get this xml as a simple text string inside controller. But can not find any solution yet. I tried this way:
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(String post, HttpServletRequest request, HttpServletResponse response){
System.out.println("post: " + post);
}
and I have contentType: 'text/xml' in my ajax config. But the variable post always printed as null.
Also I tried consumes = MediaType.APPLICATION_XML_VALUE and consumes = MediaType.TEXT_XML_VALUE in my method, but returns me HTTP Status 415 – Unsupported Media Type. What is the problem? How can I send simple xml text to my controller?
You can read your string using RequestParam:
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(
#RequestParam(value="post") String post, Model model){
...
}

Spring REST webservice with JSON param to be consumed using Jquery AJAX

I am trying to learn Spring Framework for creating RESTful web service for my future projects. So far I have tried using GET and consume it with no problem using a simple Ajax request. I have also tried using query strings to input parameters.
As of now I am trying to create an endpoint that receives a POST request. I've been researching for some days now but to no avail (some tuts are too complicated for a beginner like me).
Here is my simple code:
Java Spring
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody
public String testString(String jsonString)
{
System.out.println(jsonString);
return jsonString;
}
Ajax
var data = {"name":"John Doe"}
$.ajax({
url: "http://localhost:8080/springrestexample/test",
method:"POST",
data:data,
dataType:'text',
success: function( data ) {
alert(data);
},
error: function( xhr, status, errorThrown ) {
alert("Error:" + errorThrown + status);
}
});
I have tried debugging tomcat and it seems like I am not passing any value on the testString. Do I need to add something on my java code?
#RequestMapping only maps your method to some url.
To access data you need #RequestParam annotation to get data, eg:
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody
public String testString(#RequestParam("name") String jsonString)
{
System.out.println(jsonString);
return jsonString;
}
Look at this manual for more examples.
Since you are passing data into body from your ajax request, so you need to retrieve from the
#RequestBody
Add this annotation before the arguments like this way;
public String testString(#RequestBody String jsonString) {
System.out.println(jsonString);
return jsonString;
}
And you are done :)

How to process a multipart request consisting of a file and a JSON object in Spring restful service?

I have the following resource (implemented using Spring 4.05.RELEASE) which accepts a file and a JSON object:
(P.S. activityTemplate is a serializable entity class)
...
#RequestMapping(value="/create", method=RequestMethod.POST)
public #ResponseBody ActivityTemplate createActivityTemplate(
#RequestPart ActivityTemplate activityTemplate, #RequestPart MultipartFile jarFile)
{
//process the file and JSON
}
...
and this is the form I am testing from:
<form method="POST" enctype="multipart/form-data"
action="http://localhost:8080/activityTemplates/create">
JSON: <input type="text" name="activityTemplate" value='/* the JSON object*/'><br />
File to upload: <input type="file" name="file">
<input type="submit" value="Upload">
</form>
and this is the error that I get:
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/octet-stream' not supported
So how should I make the resource accept the JSON object as part of the multipart request, or should I be sending the form in a different way?
This took me two days to work for me!
client (angular):
$scope.saveForm = function () {
var formData = new FormData();
var file = $scope.myFile;
var json = $scope.myJson;
formData.append("file", file);
formData.append("ad",JSON.stringify(json));//important: convert to string JSON!
var req = {
url: '/upload',
method: 'POST',
headers: {'Content-Type': undefined},
data: formData,
transformRequest: function (data, headersGetterFunction) {
return data;
}
};
Spring (Boot):
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody
Advertisement storeAd(#RequestPart("ad") String adString, #RequestPart("file") MultipartFile file) throws IOException {
Advertisement jsonAd = new ObjectMapper().readValue(adString, Advertisement.class);
//do whatever you want with your file and jsonAd
Hope this should help you. You need to set the boundary in your request to inform the HTTP Request.
is simple; A brief introduction to the multipart format can be found in the below link
HTML 4.01 Specification for multipart
The following example illustrates "multipart/form-data" encoding.
If the Json Object is "MyJsonObj" , and file that need to be send is "myfile.txt", the user agent might send back the following data:
Content-Type: multipart/form-data; boundary=MyBoundary
--MyBoundary
Content-Disposition: form-data; name="myJsonString"
Content-Type: application/json
MyJsonObj //Your Json Object goes here
--MyBoundary
Content-Disposition: form-data; name="files"; filename="myfile.txt"
Content-Type: text/plain
... contents of myfile.txt ...
--MyBoundary--
or if your files is of type image with name "image.gif" then,
--MyBoundary
Content-Disposition: file; filename="image.gif"
Content-Type: image/gif
Content-Transfer-Encoding: binary
...contents of image.gif...
--MyBoundary--
You specify boundary in the Content-Type header so that the server knows how to split the data sent.
So, you basically need to select a boundary value to:
Use a value that won't appear in the HTTP data sent to the server like 'AaB03x'.
Be consistent and use the same value all over the request.
You have not given the param names to your #RequestParts ?
public #ResponseBody ActivityTemplate createActivityTemplate(
#RequestPart("activityTemplate") ActivityTemplate activityTemplate, #RequestPart("file") MultipartFile jarFile)
{
//process the file and JSON
}
Note: do not forget to include the jackson mapper .jar (maps your Json to ActivityTemplate) file in your classpath.
Couldn't you change your
#RequestMapping(value="/create", method=RequestMethod.POST)
to
#RequestMapping(value="/create",
method=RequestMethod.POST, consumes ={"multipart/form-data"})
You can use #RequestPart from org.springframework.web.bind.annotation.RequestPart; It is used as Combining #RequestBody and file upload.
Using #RequestParam like this #RequestParam("file") MultipartFile file you can upload only file and multiple single data (key value ) like
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public void saveFile(
#RequestParam("userid") String userid,
#RequestParam("file") MultipartFile file) {
}
you can post JSON Object data and and File both using #RequestPart like
#RequestMapping(value = "/patientp", method = RequestMethod.POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<?> insertPatientInfo(
#RequestPart PatientInfoDTO patientInfoDTO,
#RequestPart("file") MultipartFile file) {
}
You are not limited to using multipart file uploads directly as controller method parameters. Your form objects can contain Part or MultipartFile fields, and Spring knows automatically that it must obtain the values from file parts and converts the values appropriately.
Above method can respond to the previously demonstrated multipart request containing a single file. This works because Spring has a built-in HTTP message converter that recognizes file parts. In addition to the javax.servlet.http.Part type, you can also convert file uploads to org.springframework.web.multipart.MultipartFile. If the file field permits multiple file uploads, as demonstrated in the second multipart request, simply use an array or Collection of Parts or MultipartFiles.
#RequestMapping(value = "/patientp", method = RequestMethod.POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<?> insertPatientInfo(
#RequestPart PatientInfoDTO patientInfoDTO,
#RequestPart("files") List<MultipartFile> files) {
}
Happy To Help...
The default content type is 'application/octet-stream'. Since you are uploading jar file and JSON the content type should be set in the #RequestMapping annotation as follows:
#RequestMapping(value="/create", method=RequestMethod.POST, headers="content-type=application/json,application/java-archive")
The error message indicates that there is no HttpMessageConverter registered for a multi-part/MIME part of content type: application/octet-stream. Still, your jarFile parameter is most likely correctly identified as application/octet-stream, so I'm assuming there's a mismatch in the parameter mapping.
So, first try setting the same name for the parameter and the form's input element.
Another problem is that the JSON is uploaded as a (regular) value of a text input in the form, not as a separate part in the multi-part/MIME. So there's no content-type header associated with it to find out that Spring should use the JSON deserializer.
You can use #RequestParam instead and register a specific converter like in this answer: JSON parameter in spring MVC controller
this may help you, while receiving MultipartFile you should set request header content-type to "multipart/form-data" , then in your controller use consumes="multipart/form-data" , consumes also used to map our request to our method in controller.
If you want to receive JSON data , better to send request in the form of JSONString , just receive that jsonstring, later convert into json Object format then, use that object for yours operations.
check below code :
#RequestMapping(value="/savingImg", method=RequestMethod.POST,
consumes="multipart/form-data", produces="application/json")
public ResponseEntity<?> savingAppIMgDtlss(
#RequestParam(value="f1", required = false) MultipartFile f1 ,
#RequestParam(value="f2", required = false) MultipartFile f2 ,
#RequestParam(value="f3", required = false) MultipartFile f3 ,
#RequestParam(value="f4", required = false) MultipartFile f4 ,
#RequestParam(value="f5", required = false) MultipartFile f5 ,
#RequestParam(value="f6", required = false) MultipartFile f6 ,
#RequestParam(value="f7", required = false) MultipartFile f7 ,
#RequestParam(value="f8", required = false) MultipartFile f8 ,#RequestParam("data") String jsonString)
throws Exception , ParseException {
try{
JSONObject gstcTranObj = new JSONObject();
//converting JSONString to JSON
net.sf.json.JSONObject jsonDtls = net.sf.json.JSONObject.fromObject(jsonString);
System.out.println("f1::"+f1.getOriginalFilename());
System.out.println("f2::"+f2.getOriginalFilename());
System.out.println("f3::"+f3.getOriginalFilename());
System.out.println("f4::"+f4.getOriginalFilename());
System.out.println("f5::"+f5.getOriginalFilename());
System.out.println("f6::"+f6.getOriginalFilename());
System.out.println("f7::"+f7.getOriginalFilename());
System.out.println("f8::"+f8.getOriginalFilename());
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>("Failed",HttpStatus.NOT_FOUND);
}finally{
}
return new ResponseEntity<>("Success", HttpStatus.OK);
}
}
Exception is thrown because you don't have appropriate HttpMessageConverter, to process multipart/form-data request.
Workaround

Spring MVC REST JSON jQuery dynamic parameters for jpa query

I need some technical input for this problem:
I want to search for a contract with different parameters. For now I search for five parameters: FromDate, EndDate, Season, Name and Category. In future it should be possible to search for dynamic way of parameters. All the parameters are values of contract domain object.
var contract= {fromDate:moment($('#datepickerVon').val(), 'DD-MM-YYYY').format('DD-MM-YYYY'),
endDate:moment($('#datepickerBis').val(), 'DD-MM-YYYY').format('DD-MM-YYYY'),
season:$('#season').val(),
name:$('#name').val(),
category:$('#category').val()};
$.ajax({
url:'/contract/search/',
dataType: "json",
type: "GET",
traditional : true,
contentType: "application/json",
data: contract,
success: function(data) {
}
});
I used this controller method
#RequestMapping(value = "/search/", method = RequestMethod.GET, headers = "Accept=application/json")
#ResponseBody
public ResponseEntity<String> getContractFromSearch(
#RequestParam Map<String, String> allRequestParams, ModelMap model) {List<Vertrag> result = Contract.findAllContractsPerParameter(
allRequestParams.get("fromDate"),
allRequestParams.get("endDate"),
Season.findSeason(allRequestParams.get("season").toUpperCase()),
Name.findName(allRequestParams.get("name").toUpperCase()),
Category.findCategory(allRequestParams.get("category").toUpperCase()));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<String>(Contract.toJsonArray(result), headers,
HttpStatus.OK);
}
Season, Name, Category are dependencies of Contract. So for the jpa query I need the full Object of each. For this I want a dynamical way instead of writing similar code for all. But I'm quite sure that there is another and better solution.
May be it is possible to do it with the contract object (domain and json) itstelf and also the jpa query.
Thanks for your inputs.
from your Description of problem I guess you can develop command object named Contract
class Contract{
private Date fromDate;
private Date endDate;
private String season;
private String name;
private String category;
// Getters and setters
}
then you can pass it to your getContractFromSearch( method like below:
public ResponseEntity<String> getContractFromSearch(
#ModelAttribute Contract contract, ModelMap model) {
here Contract object will be populated by Spring Binder from your JSON data. You may need to write addition conversion logic of Date Objects.
I tried with this example and it worked for me.
The json look now like this:
contract = {fromDate:moment($('#datepickerVon').val(), 'DD-MM-YYYY').format('DD-MM-YYYY'), endDate:moment($('#datepickerVon').val(), 'DD-MM-YYYY').format('DD-MM-YYYY'), Season: {
season: "SO14"}, name: {name: "peter"}, category:{category:"SomeString"}};
console.log(contract);
$.ajax({
url:'/contracts/search/',
dataType: "json",
type: "POST",
mimeType: 'application/json',
contentType: "application/json",
data: JSON.stringify(contract),
success: function(data) {
console.log(data);
}
});
The Controller receives like this:
#RequestMapping(value = "/search/", method = RequestMethod.POST, headers = "Accept=application/json")
public ResponseEntity<String> getVertagFromSearch(#RequestBody String json, UriComponentsBuilder uriBuilder){
Contract contract = contract.fromJsonToContract(json);
//do Stuff
return new ResponseEntity<String>(Contract.toJsonArray(result), headers, HttpStatus.OK);
}
And the deserialization is here:
public static contract fromJsonToContract(String json) {
return new JSONDeserializer<Contract>().use(Calendar.class, new CalendarTransformer("dd-MM-yyyy HH:mm")).use(null, Contract.class).deserialize(json);
}

Categories