I create a function to download a CSV File. I will use that to download simple reports. I got the error below on Netbeans using Wildfly and JAX RS
RESTEASY002005: Failed executing POST /reports/downloadCSV/: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: java.io.FileWriter of media type: application/octet-stream
Here is my Code:
Controller
Update on ParametersClass
#POST
#Path("/downloadCSV")
#Produces("application/octet-stream")
public Response downloadCSV(ParametersClass param) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
FileWriter fileWriter = null;
Date date = new Date();
try {
fileWriter = new FileWriter("MainReport_"+dateFormat.format(date)+".csv");
fileWriter.append(csvService.mainReport(dateFormat.parse(param.getStartDate()),dateFormat.parse(param.getEndDate())));
fileWriter.flush();
fileWriter.close();
ResponseBuilder response = Response.ok((Object) fileWriter);
response.header("Content-Disposition","attachment; filename=\"MainReport_"+dateFormat.format(date)+".csv\"");
return response.build();
} catch (ParseException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
The csvService returns a String like:
Column1,column2,column3
cellInfo1,cellInfo2,cellInfo3
,cellInfo2,cellInfo3
cellInfo1,,cellInfo3
cellInfo1,cellInfo2,
,,cellInfo3
I tried using a different #Produces => #Produces('text/csv') , #Produces('application/octet-stream')
If I remove the Annotation #Produces I got the following error:
RESTEASY002010: Failed to execute: javax.ws.rs.NotSupportedException: RESTEASY003200: Could not find message body reader for type: class com.xo.CSVFile of content type: application/x-www-form-urlencoded;charset=UTF-8
AJAX
var dateRange = new Object();
dateRange.startDate = '2017-07-20';
dateRange.endDate = '2017-08-10';
$.ajax({
type: 'POST',
url: appPath + '/api/reports/downloadCSV/',
data: JSON.stringify(dateRange),
async:true,
success: function(data) {
}
});
What I'm doing wrong ? Could you help to me please!
.
SOLUTION
Thanks to #albert-bos
1st. Check the link in the solution from #albert-bos below.
2nd: Check this link too
3rd:
Controller:
#POST
#Path("/downloadCSV")
#Produces("text/csv")
public List<LinkedHashMap<String, String>> downloadCSV(ParametersClass param) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
return csvService.mainReport(dateFormat.parse(param.getStartDate()),dateFormat.parse(param.getEndDate()));
} catch (ParseException ex) {
return null;
}
}
MessageBodyWriter:
I create a class called CSVMessageBodyWritter (check the link) but I adpated the method writeTo:
#Override
public void writeTo(Object t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
CsvSchema schema = null;
CsvSchema.Builder schemaBuilder = CsvSchema.builder();
if(t!=null){
List<LinkedHashMap<String, String>> reportArray = (List<LinkedHashMap<String, String>>) t;
LinkedHashMap<String, String> headers = reportArray.get(0);
for (String col : headers.keySet()) {
schemaBuilder.addColumn(col);
}
schema = schemaBuilder.build().withLineSeparator("\r");
CsvMapper mapper = new CsvMapper();
mapper.writer(schema).writeValues(entityStream).writeAll(reportArray);
}
}
JAX-RS only supports a few Content-Types by default (also depending on implementation), like XML and JSON.
The idea of JAX-RS is that it will convert an object to a certain type (e.g. XML or JSON). This is so you can re-use the same object for XML and JSON, without knowing the output in your Controller. Then If you want something different like CSV, you need to define your own BodyWriter, see example here: http://www.javaprocess.com/2015/08/a-simple-csv-messagebodywriter-for-jax.html
The problem here is that your controller is to specific for CSV and isn't very flexible. You could put your output of csvService into an object and let the BodyWriter convert it into CSV.
JS
window.open("http://localhost:8080/xmlcompare-rs/xmlcompare/excelmisreport");
Rest
#GET
#Path("excelmisreport")
#Produces("application/vnd.ms-excel")
public Response getExcelReport() {
ResponseBuilder response = Response.ok((Object) file);
response.header("Content-Disposition",
"attachment; filename=MISReport.xls");
return response.build();
}
I don't have enough rep so can't add a comment for the answer of Albert Bos.
There is a PITFALL with that solution: you can suddenly run into the problem when you get empty csv file even if it should have a data. It happens because of result of getSize method.
I'm not sure which version of JAX-RS is supposed to be used in that example (in the article), but accordingly to the jax-rs documentation, result of getSize is ignored for JAX-RS 2.0, but for JAX-RS 1.X it seems to be used and "return 0" makes downloaded file empty. Make sure you return "-1". I've encountered it when tried to implement csv export in JIRA rest plugin (I guess it's based on first version of JAX-RS).
Related
I'm creating a project in Java with Spring Boot.
The focus is to receive an image that is converted to a stream and that my code converts this image to a pdf file and sends this pdf back as a stream.
Despite the analysis, I can't get past the beginning, receiving the stream.. .
Here you'll see a snippet of my postman call to the running project
My Controller looks like this:
#RestController
public class Controller {
#PostMapping(value = "/convert/{format}", consumes = "application/octet-stream", produces = "application/octet-stream")
#ResponseBody
public void convert(RequestEntity<InputStream> entity, HttpServletResponse response, #PathVariable String format, #RequestParam Map<String, String> params) throws IOException {
if ("pdf".equalsIgnoreCase(format)) {
PDFConverter cnv = new PDFConverter();
/*cnv.convert(entity.getBody(), response.getOutputStream(), params);*/
response.setContentType("application/octet-stream");
response.getOutputStream().println("hello binary");
} else {
// handle other formats
throw new IllegalArgumentException("illegal format: " + format);
}
}
}
What do I overlook in this case?
I found the solution, in the controller I used RequestEntity<InputStream> entity, this gave the error. After changing this to HttpServletRequest request it worked.
#RestController
public class Controller {
#RequestMapping(value="/convert/{format}", method=RequestMethod.POST)
public #ResponseBody void convert(HttpServletRequest request, HttpServletResponse response, #PathVariable String format, #RequestParam Map<String, String> params) {
try{
if ("pdf".equalsIgnoreCase(format)) {
PDFConverter cnv = new PDFConverter();
response.setContentType("application/pdf");
cnv.convert(request.getInputStream(), response.getOutputStream(), params);
} else {
// handle other formats
throw new IllegalArgumentException("illegal format: " + format);
}
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
}
As the error message tells you already, your content-type is not valid. You expecting a different content Type than you are sending off. Might be the problem that you append the charset definition to the request.
I think you are using commons-fileupload's streaming API. This won't work if spring.http.multipart.enabled=true, due to the request being pre-processed. Can you try setting spring.http.multipart.enabled=false and also change consumes = { MediaType.MULTIPART_FORM_DATA_VALUE },
I'm struggling with creating valid request to some internal service using java spring. The problem is with proper payload for multipart/form-data boundary.
Environment:
java server -> (rest) http multipart/form-data -> some service
(there is no browser in the middle)
Valid payload should look like this:
------WebKitFormBoundaryp8mrQWOb5GiyC90y
Content-Disposition: form-data; name="files"; filename="0000.png"
Content-Type: image/png
[binary data]
------WebKitFormBoundaryp8mrQWOb5GiyC90y--
Unfortunately I'm unable to change this "headers" and I'm getting something like this:
--fkGT7CJaQB9-2aa8G1ePv17iHKnWSsd
Content-Disposition: form-data; name="files"
Content-Length: 170096
[binary data]
--fkGT7CJaQB9-2aa8G1ePv17iHKnWSsd--
I've searched many stackoverlow questions, but nothing seems to work.
This is what I've done till now (generates above payload):
HashMap<String, List<String>> additionalHeaders = new HashMap<>();
String fileMd5 = "tgrlfG0pjblWZB6g1f7j5w=="; //#todo
File file = new File(systemFile.getAbsoluteFileLocation());
Path filePath = Paths.get(systemFile.getAbsoluteFileLocation());
try{
DiskFileItem fileItem = new DiskFileItem("file", "image/png", false, file.getName(), (int) file.length() , file.getParentFile());
InputStream input = new FileInputStream(file);
OutputStream os = fileItem.getOutputStream();
int ret = input.read();
while ( ret != -1 )
{
os.write(ret);
ret = input.read();
}
os.flush();
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
MultiValueMap<String, Object> parts =
new LinkedMultiValueMap<>();
ByteArrayResource resource = new ByteArrayResource(multipartFile.getBytes());
parts.add("files", resource);
additionalHeaders.put("Content-MD5", Collections.singletonList(fileMd5));
additionalHeaders.put("Content-Disposition", Collections.singletonList("attachment; filename=\""+systemFile.getFilenameWithExtension()+"\""));
ResponseEntity<FrpFileServer> responseEntity = formDataRestClient.post(this, parts, FrpFileServer.class, isServerSide, frpToken.getTokenId(), additionalHeaders, MediaType.MULTIPART_FORM_DATA);
return responseEntity.getBody();
} catch (IOException e) {
return null;
}
formDataRestClient builds the rest of the request via RestTemplate
public <K, T> ResponseEntity<T> post(RestClientInterface reference, K requestClass, Class<T> responseClass, boolean isServerSide, String resourceId, HashMap<String, List<String>> additionalHeaders, MediaType contentType) {
Ok, I've fixed my problem with replacing resource creation with this:
ByteArrayResource resource = new ByteArrayResource(multipartFile.getBytes()){
#Override
public String getFilename() {
return systemFile.getFilenameWithExtension();
}
};
Thanks to that, restTemplate handles it's magic in proper way :)
I wrote one generic REST client which you can use in any java based application or framework, see GIT source of delete method how to load anything in request header where i am passing Authorization- https://github.com/gajeralalji/JAVA-REST-Client/wiki
check source of REST-client.java class and let me know if you are still facing any issue.
To pass a value to java I have used post method from AngularJs to my java code and it works just fine to do what i needed and my code is as the followoing :
#RequestMapping(value="/shelf", method=RequestMethod.POST, consumes="application/json")
public void getSelectedShelf(#RequestBody String shelf) throws JSONException {
logger.debug("Start uploading the files...");
logger.debug(shelf.toString());
JSONObject json;
try {
json = new JSONObject(shelf);
} catch (JSONException e) {
throw new IllegalArgumentException("Invalid JSON Payload: " + shelf, e);
}
selectedShelf= json.getLong("shelf");
logger.debug(selectedShelf+"");
logger.info("Json Payload = " + json);
}
, but at the end of my java method, I see the following error:
"org.thymeleaf.exceptions.TemplateInputException: Error resolving template "shelf", template might not exist or might not be accessible by any of the configured Template Resolvers
I don't have a template for /shelf and I don't want to have it as I just want to pass a value to my class. How can I resolve this problem ?
As #Tommy Schmidt has explained very well, if you don't want to return anything in a post method you should add #ResponseStatus(value = HttpStatus.OK) to the method.
Before I ask my question I have to say that I have read more than 20 questions and articles about this problem and none of them could solve it.
My problem is I have a restful server in java like this:
#RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
#ResponseBody
public void downloadByCode(#RequestBody final String stringRequest, final HttpServletResponse response)
{
try
{
final ObjectMapper objectMapper = new ObjectMapper();
final JsonNode jsonRequest = objectMapper.readValue(stringRequest, JsonNode.class);
// ...
// some processings here to create the result
// ....
final ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result);
// Flush the result
outputStream.flush();
}
catch (final Exception exception)
{
LOG.debug("Exception Thrown [downloadByCode]", exception);
}
}
And I have tried different ways to send a json to this server with jquery (but all of them create errors):
$.ajax({
url:"/downloadByCode",
type:"POST",
data: JSON.stringify({"name":"value"}) });
415 "errors message" : "Content type 'application/x-www-form
urlencoded;charset=UTF-8' not supported", "type" :
"HttpMediaTypeNotSupportedError"
So I tried to fix it by adding contentType:
$.ajax({
url:"/downloadByCode",
contentType:"application/json",
type:"POST",
data: JSON.stringify({"name":"value"}) });
400 "errors message" : "Could not instantiate JAXBContext for class
[class java.lang.String]: null; nested exception is
javax.xml.bind.JAXBException\n - with linked
exception:\n[java.lang.NullPointerException", "type"
:"HttpMessageConversionError"
I tried to send json object directly instead of JSON.stringify and it gives the same 400 error.
I tried to add different consumes to the #requestMapping but still no luck.
I tried to define my own class instead of JsonNode but it does not change anything.
Any ideas?
Please try to create new class :
public class InputData{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
Then
public void downloadByCode(#RequestBody InputData stringRequest, final HttpServletResponse response)
And
$.ajax({
url:"/downloadByCode",
contentType:"application/json",
type:"POST",
data: JSON.stringify({"name":"value"}) });
try #RequestBody final Map<String, String> stringRequest
also you will need consumes = "application/json" on the #RequestMapping because you have that in your AJAX call
You will get 400 if spring doesn't like the format in which you send your ajax - I've had so much trouble with this in the past and it seems better to just ignore header types and content types unless necessary
You might try sending your response back as a ResponseEntity instead of using the HttpServletResponse directly. My hunch is that second argument, the HttpServletRequest argument, is what is causing the problem. I've never used that. I've always send my response back using the spring mvc api.
With Jersey api you can try just:
#POST
public void downloadByCode(String stringRequest)
and I think you'll find the body of your post in stringRequest.
You can take request body as string with usage of org.springframework.http.HttpEntity<String> as request type, here is example with your code as base:
#RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
#ResponseBody
public void downloadByCode(final HttpEntity<String> request, final HttpServletResponse response)
{
try
{
final ObjectMapper objectMapper = new ObjectMapper();
final JsonNode jsonRequest = objectMapper.readValue(request.getBody(), JsonNode.class);
// ...
// some processings here to create the result
// ....
final ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result);
// Flush the result
outputStream.flush();
}
catch (final Exception exception)
{
LOG.debug("Exception Thrown [downloadByCode]", exception);
}
}
But maybe it will be better to use also String as return type, if you are planning to return result as string value, like this:
#RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
#ResponseBody
public String downloadByCode(HttpEntity<String> request) {
String requestBody = request.getBody();
String result;
// ...
// some processings here to create the result text
// ....
return result;
}
I made simple application using Spring Boot with usage of proposed solutions using HttpEntity and also additional example of usage POJO, to run application you need to have Maven and JDK >= 1.7.
#clonning repository with sample
git clone git#github.com:mind-blowing/samples.git
#change current folder to sample application root
cd samples/spring-boot/rest-handler-for-plain-text
#running application using maven spring-boot plugin
mvn spring-boot:run
After application will be started you can open http://localhost:8080 and you will see html page with simple usage of JQuery to send POST requests, text of request and response will visible on html page, in controller I added two handlers, first with usage of HttpEntity and second with usage of POJO.
Controller: SampleRestController.java
HTML page: index.html
Project: https://github.com/mind-blowing/samples/tree/master/spring-boot/rest-handler-for-plain-text
First of all If you are using maven you should add dependency for jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.1</version>
</dependency>
or you can download the jar and put it in our project class path (you can use other mapper as well)
then you should create a model or DTO class where you can map your json
public class Data{
private String name;
pubilc Data(){}
//getter and setter
}
THEN you controller
#RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
#ResponseBody
public Data downloadByCode(#RequestBody final Data data, final HttpServletResponse response)
{
//your code
return data;
}
AJAX CALL
$.ajax({
url:"/downloadByCode",
contentType:"application/json",
type:"POST",
data: JSON.stringify({"name":"value"}) });
(Optional)You can override behavior by telling object mapper not to fail on missing properties by defining the bean as follows:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false));
return converter;
}
http://websystique.com/springmvc/spring-mvc-requestbody-responsebody-example/
Looking at your errors, it's clear that you have configured 'Jaxb2RootElementHttpMessageConverter' or similar XML converter in your spring configuration. And since you have registerned an XML converter, the #RequestBody and #ResponseBody work based on the registered message converters.
So, to solve your problem, go with a JSON message converter such as 'MappingJacksonHttpMessageConverter'. Once you register a JSON message converter, create a bean class to hold your json data and use it with RequestBody as below:
// It has to meet the json structure you are mapping it with
public class YourInputData {
//properties with getters and setters
}
Update 1:
Since you have defined multiple message converters, Spring tries to use the first one available by default. In order to use specific message converter(in this case Jackson converter), you should specify 'Accept' header from client like below:
$.ajax({
headers: {
"Accept" : "application/json; charset=utf-8",
"Content-Type": "application/json; charset=utf-8"
}
data: "data",
success : function(response) {
...
} })
The final answer is a combination of a number of answers/comments in this question that I am going to summarize them here:
1- You have to make sure you have an appropriate json converter in your spring config such as MappingJacksonHttpMessageConverter (credits to #java Anto)
2- You have to create a POJO class with same structure as your json object (see #Vinh Vo answer)
3- Your POJO class cannot be an inline class unless it is a static class. It means it should have its own java file or it should be static. (credits to #NTyler)
4- Your POJO class can miss parts of your json object if you set it appropriately in your object mapper (see #Aman Tuladhar answer)
5- Your ajax call requires contentType:"application/json", and you should send your data with JSON.stringify
Here is the Final code that is working perfectly:
public static class InputData
{
private String name
public String getName()
{
return name;
}
public void setName(final String name
{
this.name = name;
}
}
#RequestMapping(value = "/downloadByCode", method = RequestMethod.POST)
#ResponseBody
public void downloadByCode(#RequestBody final InputData request, final HttpServletResponse response)
{
try
{
String codes = request.getName();
// ...
// some processings here to create the result
// ....
final ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result);
// Flush the result
outputStream.flush();
}
catch (final Exception exception)
{
LOG.debug("Exception Thrown [downloadByCode]", exception);
}
}
And it is the jquery Ajax request:
$.ajax({
url:"/downloadByCode",
contentType:"application/json",
type:"POST",
data: JSON.stringify({"name":"value"}) });
Delete the #ResponseBody on your downloadByCode method
Change your method downloadByCode() return type to String and then return the String
Response body will automatically convert the returned String to JSON and then use the data appropriately
I am not that well versed with java but as much as I know your java code must be something like this.
public class downloadByCode{
#GET
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public Response downloadByCode(#QueryParam("paramater1") final String parameter 1, #Context HttpServletRequest httpRequest) {
If this not helps you can keep you code somewhere and share it.
Can I have a rest service that can be used for file upload i.e. multi-part form data and JSON parameter? Below is the example of the service.
#POST
#Path("/upload")
#Consumes({ MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_JSON })
public Response uploadFile(#FormDataParam("file") InputStream uploadedInputStream,#FormDataParam("file") FormDataContentDisposition fileDetail, City city){
The problem is while testing I am trying to pass both file as an attachment and city object as JSON, it is giving me error as Content-Type could either be application/json or multipart/form-data.
Let me know if there is any way to handle this
You may Use Any Client Side Language to submit form with MultipartFile and Json data. I am writing Java Code in Spring MVC here. It will send String Json and MultiPartFile. then Me going to to Cast String JSON to Map, and Save File at Desired Location.
#RequestMapping(value="/hotel-save-update", method=RequestMethod.POST )
public #ResponseBody Map<String,Object> postFile(#RequestParam(value="file", required = false) MultipartFile file,
#RequestParam(value = "data") String object ){
Map<String,Object> map = new HashMap<String, Object>();
try {
ObjectMapper mapper = new ObjectMapper();
map = mapper.readValue(object, new TypeReference<Map<String, String>>(){});
}catch (Exception ex){
ex.printStackTrace();
}
String fileName = null;
if (file != null && !file.isEmpty()) {
try {
fileName = file.getOriginalFilename();
FileCopyUtils.copy(file.getBytes(), new FileOutputStream(servletContext.getRealPath("/resources/assets/images/hotelImages") + "/" + fileName));
} catch (Exception e) {
header.put(Utils.MESSAGE, "Image not uploaded! Exception occured!");
return result;
}
}
}
Can't you leave the #Consumes off and check the Content-Type header in the method itself, deciding what to do in code?
Your problem seems to be a restriction in the functionality of that annotation (is it Spring MVC?)
I have solved my problem by passing JSON as String from client and then converting String to JSON object.
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(#FormDataParam("file") InputStream uploadedInputStream,
#FormDataParam("file") FormDataContentDisposition fileDetail, #FormDataParam("city") String city){