Spring MVC: how to download Excel from DB - spring

I have a problem with Spring MVC and Excel. I store excel file in database as lob. Like this part of my entity database is PostgreSQL
#Lob
#Column(name = "Exel")
private String exel;
Next I would like to get it from DB and user can download it from web page this is the controller
#RequestMapping(value = "/downloadExelTemplate.xls", method = RequestMethod.GET)
public void downloadExelTemplate(HttpServletResponse response)
throws IOException {
response.setContentType("application/x-msexcel");
ExelDTO exel = service.getExel(new Long(1));
InputStream is = new ByteArrayInputStream(exel.getExel().getBytes());
BufferedWriter outex = new BufferedWriter(new FileWriter("out.xls"));
outex.write(exel.getExel());
outex.close();
ServletOutputStream out = response.getOutputStream();
out.write(exel.getExel().getBytes());
is.close();
out.close();
}
And I get not proper xls file with the bugs.
Please help me. What is wrong? When I get file from stream is the same effect.

So I resolve my problem, I change the type of data in hibernate entity to
#Lob
byte[] exelData;
and everything work well. When there was only text file there was no problem to mapping the #lob to the string but connected with ms office formats it fails. So I learned how to keep data file correctly.

Related

Problem With outputStream after 1 download

After downloading an Excel with HttpServletResponse the second time i tried to download it dosent work.
public void DatosaExcel(HttpServletResponse response) throws IOException {
writeHeaderLine();
writeDataLines();
ServletOutputStream outputStream = response.getOutputStream();
workbook.write(outputStream);
outputStream.close();
}
#GetMapping("/DescargarExcelEmpleados")
public void DescargarEmpleados(HttpServletResponse response) throws IOException {
response.setContentType("application/octet-stream");
String headerKey = "Content-Disposition";
String headerValue = "attachment; filename=empleados.xlsx";
response.setHeader(headerKey, headerValue);
productService.DatosaExcel(response);
}
those are my Ctrl and function that calls the excel generator but i cant find a way of solving this issue
I'm guessing you are using Apache POI for Excel work here and also guess that nothing happens when you try to generate it the second time (if there is an error, please provide the log).
This is probably because you didn't close the workbook after write. Always
workbook.write(outputStream);
workbook.close(); //<-- Important!!!
I found the answer i have the workbook declaration XSSFWorkbook workbook = new XSSFWorkbook();, on the service class, so after 1 download the object was already full. so a work aroundi used was in the controller function call i initialized the object again.
workbook = new XSSFWorkbook();

Spring boot large file upload and download support

I have a spring boot web application which will handle large file (max size of 5g) upload and then save it to s3. The request and response may last for a long time.
So what is the best practice to handle the upload and download like this? How to make a good performance to prevent my server down when download or upload large files?
you can use multipart/form-data
#RequestMapping(value = "/agency/create", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity<List<String>> createAgency(
#RequestParam(value = "username", required = true) String username,
#RequestParam(value = "pic1", required = true)MultipartFile pic1File,
MultipartHttpServletRequest request, ModelAndView modelAndView) {
List<String> requestKeys=new ArrayList<String>();
List<String> originalFileName=new ArrayList<String>();
request.getFileNames().forEachRemaining(requestKeys::add);
for(String multipartFile:requestKeys) {
originalFileName.add(request.getFile(multipartFile).getOriginalFilename());
}
storageService.store(pic1File);
return new ResponseEntity<List<String>>(originalFileName, HttpStatus.CREATED);
}
Posting in case someone finds this useful in the future. This works with a REST controller as of Spring Boot 2.4.2.
Class annotations:
#RestController
#RequestMapping("/api")
Method declaration:
#RequestMapping(path = "/file-upload/{depot}/{fileName}", method = {RequestMethod.POST, RequestMethod.PUT})
public ResponseEntity<String> fileUpload(
#PathVariable(name = "depot") String depot,
#PathVariable(name = "fileName") String fileName,
InputStream inputStream,
HttpServletRequest request,
HttpServletResponse response)
The above is the Spring Boot configuration for a REST Controller that worked for me for large file upload. The key was adding InputStream inputStream directly.

Stream the data content directly from database to the HTTP

Right now we are holding file in our postgresql database and mapping that content using byte[] field in our entity. I need to investigate if we could
stream the content data direct from the database to the HTTP output stream, and do the same thing in opposite way so stream binary data from HTTP into database using jpa Blob data type. I know that Blob has methods getBinaryStream and setBinaryStream so its may work, and we do not need hold data into memory.
What I am concern are database transaction, because we are mapping entity into DTO, and the second thing is broken Http request and data may be lost in some point.
Is there are anybody who had any experience with that solution ?
Solution for stream-reading data from BLOBs:
Existing BLOB data are streamed by passing OutputStream (provided by the servlet container) into transactional method which writes entity blob data to the stream from inside transaction. Note that the content type of response is set before writing the data.
Entity class:
public class Attachment {
private java.sql.Blob data;
public java.sql.Blob getData() { return data; }
}
Service method:
#Transactional(readOnly = true)
public void copyContentsTo(long attachmentId, OutputStream outputStream) throws IOException {
Attachment dbAttachment = attachmentRepository.findOne(attachmentId);
try (InputStream is = dbAttachment.getData().getBinaryStream()) {
IOUtils.copy(is, outputStream);
} catch (SQLException e) {
throw new ParameterException("Cannot extract BLOB for attachment #" + attachmentId, e);
}
}
REST API Spring Controller method:
#GetMapping(value = "/api/project-attachment/{attachment-id}/content")
#ResponseStatus(HttpStatus.OK)
public void getAttachmentContent(
#PathVariable("attachment-id") long attachmentId,
HttpServletResponse response,
OutputStream stream) throws IOException {
response.setContentType(getMime(attachmentId));
attachmentService.copyContentsTo(attachmentId, stream);
}
Lucasz, Spring Content for JPA does exactly what you are asking. Designed to make it really easy to create Spring applications that handle content (documents, images, video's etc). It supports a range of backend stores one of which being relational DBs and obviously they uses BLOBs.
This JPA module will stream uploaded files from the request input stream directly to the database and vice versa thus it never stores the entire file in-memory which would obviously cause problems with very large files.
It would save you from having to write ANY of the code in #tequilacat's answer.
Probably worth a look.

Send list of files via Spring Boot controller

I have the following controller:
#RequestMapping(value = "/url", method = RequestMethod.GET)
public List<ResponseEntity<InputStreamResource>> getLayouts(#RequestParam("app") String app, HttpServletResponse response) {
.....
inputStreamResource = new InputStreamResource(new FileInputStream(file));
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/xml");
responseHeaders.setContentLength(file.length());
responseHeaders.add(HttpHeaders.CONTENT_ENCODING, "UTF-8");
ResponseEntity inputStreams = new ResponseEntity(inputStreamResource, responseHeaders, HttpStatus.OK);
inputStreamResources.add(inputStreams);
.....
}
So the problem is that it doesn't work. First I had the problem:
No serializer found for class java.io.FileDescriptor
and no properties discovered to create BeanSerializer
(to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
Then I added:
objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
And problem disappeared, but arise the new one: I see that my inputStream repeated many times and now I have an error:
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.5.jar:2.6.5]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.5.jar:2.6.5]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) ~[jackson-databind-2.6.5.jar:2.6.5]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.6.5.jar:2.6.5]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) ~[jackson-databind-2.6.5.jar:2.6.5]
My question what's wrong I do or missed something? Is there better way to send list of files, maybe my approach is wrong in general?
Depends what you want to do, if you want to return paths and content then I would return something like ResponseEntity<List<FileResource>> where FileResource would be your own class:
public class FileResource {
private String fileName;
private byte[] content;
// getters, setters
}
But be aware though that this approach may consume a lot of memory and also result in huge responses - for larger files you may want to return only a list of file names and then let the client request the files one by one, whereupon you'd stream the file content directly to output. Or you could zip the files in a single archive and return the archive to the client (like many cloud storages do).
You may also want to look at https://spring.io/guides/gs/uploading-files/

Spring returning image as ResponseEntity<byte[]> - image corrupt

I am working on a spring 3.2.7 app and it sends signatures stored in the database as base64 string back to the users browser via a spring controller which outputs the byte array ResponseEntity.
The image is always corrupted and I havent worked on this part of the system as I double checked in svn and the controller has not been touched since the branch I am working on was created.
I am able to convert the base64 string to an image on my desktop and I am also able to the convert the byte array returned to browser into an image before spring steps in.
Below is my code, this was apparently working before so perhaps there is some config change that could cause this?
#RequestMapping(value = "/submissions/signature/{type}/{id}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<byte[]> getSignature(#PathVariable String type, #PathVariable Integer id) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String base64 = ... gets from db
byte[] bytes = Base64.decodeBase64(base64);
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(bytes));
ImageIO.write(bi, "png", baos);
HttpHeaders headers = new HttpHeaders();
headers.setLastModified(Calendar.getInstance().getTime().getTime());
headers.setCacheControl("no-cache");
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentLength(baos.toByteArray().length);
//Image as base64 string is ok in converter
System.out.println("BASE 64 IMAGE IS: " + base64);
//This image is created ok on desktop
FileOutputStream fos = new FileOutputStream("C:\\Users\\p\\Desktop\\test_signature.png");
fos.write(bytes);
fos.close();
//This image is created ok on desktop
FileOutputStream fos3 = new FileOutputStream("C:\\Users\\p\\Desktop\\test_signature_baos.png");
fos3.write(bytes);
fos3.close();
return new ResponseEntity<byte[]>(baos.toByteArray(), headers, HttpStatus.OK);
}
The image is being rendered in the browser like:
<img id="userSignature" width="296" height="110" style="border:0px" src="/webapp/service/submissions/signature/user/${subid}" alt="User signature" />
I have not changed this class and I am told that it did work, I am able to create images from both byte arrays and they are ok and looks the same and I able to render the signature string ok for testing like:
<IMG SRC="data:image/png;base64, <base_64_string>" ALT="">
has anyone experienced similar issues or know what could be causing this?
I have now tried sending an image from my file system already created as png and that also fails.
I have now noticed that CSV files do not download properly in the app and they stream in the same way:
#RequestMapping(value = "/results/csv", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<byte[]> getResultsInCsvFormat() throws IOException {
I have successfully returned file contents with the help of an InputStream:
#RequestMapping(value = "/submissions/signature/{type}/{id}",
method = RequestMethod.GET)
public HttpEntity getFile(HttpServletResponse response,
#PathVariable String type,
#PathVariable Integer id) {
String base64 = "foo"; // get base-64 encoded string from db
byte[] bytes = Base64.decodeBase64(base64);
try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
StreamUtils.copy(inputStream, response.getOutputStream());
response.setContentType(MediaType.IMAGE_PNG_VALUE);
} catch (IOException e) {
// handle
}
return new ResponseEntity(HttpStatus.OK);
}
Note that I'm not using ResponseBody, and in my working version I am using MediaType.APPLICATION_OCTET_STREAM_VALUE rather than the actual file content type.
The accepted solution didn't work for me in Spring Boot 2.x for Base64 images. Here is how I returned a Base64 image:
#GetMapping(value = "/pixels/{id}", produces = MediaType.IMAGE_PNG_VALUE)
#ResponseBody
public byte[] pixelTracking(#PathVariable String id) {
// TODO: do whatever you want here
// return png image
String base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=";
return Base64.getDecoder().decode(base64);
}
Ok so I have fixed this now with some thanks to beerbajay who showed me that downloading by streaming to the response directly was ok and also that I should look at ByteArrayHttpMessageConverter.
It turned out that I had made a mistake in the spring config and I realised this after reading the spring documentation that told me that ByteArrayHttpMessageConverter is registered automatically when using <mvc:annotation-driven />.
The mvc annotation driven tag had been removed from the config as I thought this was doing the same thing (and I thought it only needed to be declared once within a spring context):
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
It looks like this alone isnt sufficient to set up annotation-driven in the app and I have added back in the tag to the context.xml file.
I dont understand why this needs to be in two places though as all the xml config is loaded through same xml file in what I assumed was the same spring context:
<import resource="config/properties.xml" />
<import resource="config/security.xml" />
<import resource="config/context.xml" />
<import resource="config/content-negotiation.xml" />
<import resource="config/rest-client.xml" />
my solution is :
backend is spring boot and frontend is reactjs
bug is different syntax is backend and frontend : java use ("_","-") and web(reactjs,...) use ("/","+") |
for example : "PGjQOA66-_ne-" convert to "PGjQOA66+//ne/+"
you can testing base64 in this link :https://onlinepngtools.com/convert-base64-to-png
back end code :
ByteArrayOutputStream baos = new ByteArrayOutputStream();
File file = new File(path);/// path : is external directory and local directory in server
FileInputStream imageInFile = new FileInputStream(path);
byte imageData[] = new byte[(int) file.length()];
imageInFile.read(imageData);
String base64 = encodeImage(imageData);
/// 1. Not show if size up 300KB !!! :|
/// 2. base64.replaceAll("_","/").replaceAll("-","+")
byte[] bytes = Base64.decodeBase64(base64.replaceAll("_","/").replaceAll("-","+"));
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(bytes));
ImageIO.write(bi, "png", baos);
HttpHeaders headers = new HttpHeaders();
headers.setLastModified(Calendar.getInstance().getTime().getTime());
headers.setCacheControl("no-cache");
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentLength(baos.toByteArray().length);
return new ResponseEntity<byte[]>(baos.toByteArray(), headers, HttpStatus.OK);
and other solution for back end:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
File file = new File(path);/// path : is external directory file and local directory file in server, or path get of database.
FileInputStream imageInFile = new FileInputStream(path);
byte imageData[] = new byte[(int) file.length()];
imageInFile.read(imageData);
/// 1. Not show if size up 300KB !!! :| i dont now!
/// 2. base64.replaceAll("_","/").replaceAll("-","+")
String base64 = encodeImage(imageData).replaceAll("_","/").replaceAll("-","+");
return base64;
and reactjs code is :
const [imageData, setImageData] = React.useState({});
setImageData(...request to backend);
<img src={`data:image/png;base64,${imageData}`} alt="This Is alert" />

Resources