Play Framework : Sending CSV file as an attachment - java

How to send a CSV file as an attachment in Play framework? I looked into the Play documentation by I didn't find what I'm looking for. I know I can return an input stream like that : ok(java.io.InputStream content, int chunkSize) but how to create and send the content to the csv file?

Play makes it easy to manipulate a HTTPResponse. You can find in the manual, how to add headers, set cookies etc. in a response.
The implementation differs according to the use case:
Some string source is converted to csv file while sending to the client.
An existing csv file is sent to the client.
Sending string as CSV file in a HTTPResponse
Play 2.4
static private final String CSV_FILE_NAME = "demo.csv";
...
public Result string2CsvFile() {
String csvContent = "f1,f2,f3,f4";
response().setContentType("text/csv");
response().setHeader(CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", CSV_FILE_NAME));
return ok(new ByteArrayInputStream(csvContent.getBytes()));
}
Play 2.5
static private final String CSV_FILE_NAME = "demo.csv";
...
public Result string2CsvFile() {
String csvContent = "f1,f2,f3,f4";
response().setHeader(CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", CSV_FILE_NAME));
return ok(new ByteArrayInputStream(csvContent.getBytes())).as("text/csv");
}
Sending existing CSV file in a HTTPResponse
Play 2.4
static private final String CSV_FILE_NAME = "demo.csv";
...
public Result attachCsvFile() {
response().setContentType("text/csv");
response().setHeader(CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", CSV_FILE_NAME));
return ok(Play.application().getFile(CSV_FILE));
}
Play 2.5
static private final String CSV_FILE_NAME = "demo.csv";
...
public Result attachCsvFile() {
response().setHeader(CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", CSV_FILE_NAME));
return ok(Play.current().getFile(CSV_FILE)).as("text/csv");
}

Sounds like you're looking to do something like this:
public Result downloadCsv() {
response().setHeader("Content-disposition", "attachment; filename=example.csv");
return ok(Play.application().getFile("conf/resources/example.csv"));
}

PlayFramework already have a method to delivery files as attachments (see Results.ok too). Just do the following:
public Result deliveryFile() {
File file = ...
boolean inline = true;
return ok(file, inline);
}
Keep in mind that is up to you to ensure the file exists and is readable.

Related

Return a zip (or any file) from the server on the client browser (REST)

So I am using Java for my Server and Angular for the Client. I am currently working on a feature where you can select multiple files from a table and when you press on download, it generates a zip file and downloads it to your browser. As of right now, the server now creates the zip file and I can access it in the server files. All that is left to do is to make it download on the client's browser. (the zip file is deleted after the client downloads it)
After doing some research, I found out that you can use a fileOutputStream to do this. I also saw some tools like retrofit... I am using REST and this is what my code looks like. How would I achieve my goal as simply as possible?
Angular
httpGetDownloadZip(target: string[]): Observable<ServerAnswer> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<ServerAnswer>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
Java zipping method
public void getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
ZipUtil.pack(new File("Download"), new File("selected-files.zip"));
// what do I return ???
return;
}
Java context
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
Controller.getDownloadZip(files, folderName);
// what do I return to download the file on the client's browser ????
return;
}
});
A possible approach to successfully download your zip file can be the described in the following paragraphs.
First, consider returning a reference to the zip file obtained as the compression result in your downloadZip method:
public File getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
File selectedFilesZipFile = new File("selected-files.zip")
ZipUtil.pack(new File("Download"), selectedFilesZipFile);
// return the zipped file obtained as result of the previous operation
return selectedFilesZipFile;
}
Now, modify your HttpHandler to perform the download:
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
// exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
// Download the file. I used java.nio.Files to copy the file contents, but please, feel free
// to use other option like java.io or the Commons-IO library, for instance
exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
try (OutputStream responseBody = httpExchange.getResponseBody()) {
Files.copy(selectedFilesZipFile.toPath(), responseBody);
responseBody.flush();
}
}
});
Now the problem is how to deal with the download in Angular.
As suggested in the previous code, if the resource is public or you have a way to manage your security token, including it as a parameter in the URL, for instance, one possible solution is to not use Angular HttpClient but an anchor with an href that points to your ever backend handler method directly.
If you need to use Angular HttpClient, perhaps to include your auth tokens, then you can try the approach proposed in this great SO question.
First, in your handler, encode to Base64 the zipped file contents to simplify the task of byte handling (in a general use case, you can typically return from your server a JSON object with the file content and metadata describing that content, like content-type, etcetera):
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Download the file
byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
byte[] base64Data = Base64.getEncoder().encode(fileContent);
exchange.sendResponseHeaders(200, base64Data.length);
try (OutputStream responseBody = httpExchange.getResponseBody()) {
// Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing
// the base64 data to the response outputstream
IOUtils.write(base64Data, responseBody);
responseBody.flush();
}
}
});
After that, use the following code in you client side Angular component to perform the download:
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
tap((b64Data) => {
const blob = this.b64toBlob(b64Data, 'application/zip');
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl);
})
).subscribe()
As indicated in the aforementioned question, b64toBlob will look like this:
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
Probably you will need to slightly modify the httpGetDownloadZip method in your service to take into account the returned base 64 data - basically, change ServerAnswer to string as the returned information type:
httpGetDownloadZip(target: string[]): Observable<string> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<string>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
You could try using responseType as arraybuffer.
Eg:
return this.http.get(URL_API_REST + 'download?filename=' + filename, {
responseType: 'arraybuffer'
});
In My Project including both front end (angular) and back end (java).
We used the below solution ( hope it work for you ):
Angular:
https://github.com/eligrey/FileSaver.js
let observable = this.downSvc.download(opts);
this.handleData(observable, (data) => {
let content = data;
const blob = new Blob([content], { type: 'application/pdf' });
saveAs(blob, file);
});
Java:
public void download(HttpServletRequest request,HttpServletResponse response){
....
response.setHeader("Content-Disposition",
"attachment;filename=\"" + fileName + "\"");
try (
OutputStream os = response.getOutputStream();
InputStream is = new FileInputStream(file);) {
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) > -1) {
os.write(buf, 0, len);
}
os.flush();
}
You can still use HttpServletRequest on the server...
Then get its OutputStream and write to it.
#RequestMapping(method = RequestMethod.POST , params="action=downloadDocument")
public String downloadDocument(#RequestParam(value="documentId", required=true) String documentId,
HttpServletRequest request,
HttpServletResponse response )
{
try {
String docName = null;
String documentSavePath = getDocumentSavePath();
PDocument doc = mainService.getDocumentById(iDocumentId);
if(doc==null){
throw new RuntimeException("document with id: " + documentId + " not found!");
}
docName = doc.getName();
String path = documentSavePath + ContextUtils.fileSeperator() + docName;
response.setHeader("Content-Disposition", "inline;filename=\"" + docName + "\"");
OutputStream out = response.getOutputStream();
response.setContentType("application/word");
FileInputStream stream = new FileInputStream(path);
IOUtils.copy(stream, out);
out.flush();
out.close();
} catch(FileNotFoundException fnfe){
logger.error("Error downloading document! - document not found!!!! " + fnfe.getMessage() , fnfe);
} catch (IOException e) {
logger.error("Error downloading document!!! " + e.getMessage(),e);
}
return null;
}

How to convert content of MultipartFile file into JSON string

I have one file Person.Java
public class Person {
private String firstName;
private String lastName;
}
file is stored at location say : "C:\Users\Person.Java"; This file is not part of the project.
I have written REST API endpoint which is accepting this file as a input and reading this file.
#PostMapping(value = "/poc/jsobj", produces = {APPLICATION_JSON_VALUE})
public ResponseEntity<ResponseMessage> convertToJson(#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
String completeData = new String(bytes);
//what should I write here
}}
After reading I want to convert content of this file into JSON format.
{
"firstName":" ",
"lastName":" "
}
so, the requirement is take java file (a class) as input to controller and generate corresponding json String as it's output.

Java Json beautifying on save

I am saving a player to a .json file like this:
public static void savePlayer(Player player) {
final String username = player.getUsername();
final byte[] json = new Gson().toJson(player).getBytes();
final String path = "pack/players/" + username;
try {
Files.write(Paths.get(path + "/data.json"), json, StandardOpenOption.CREATE);
logger.info("Successfully SAVED player [username=%s]!", username);
} catch (IOException e) {
e.printStackTrace();
}
}
and I am using this Eclipse JSON editor plugin to view the json file: https://sourceforge.net/projects/eclipsejsonedit/
However, when looking at the file it is all compressed in one line and not beautified.
The resulting one line of json code is printed like this (instead of beautified):
{"inventory":{"data":[null]},"equipment":{"data":[null]},"playerCredentials":{"username":"kay","password":"password"},"attribute":{}}
What can I do to beautify the code before saving it?
Since you are using Gson you can use their prettyPrinting and create a new Gson obejct
Gson gson = new GsonBuilder().setPrettyPrinting().create();
And then create a String object from that
String output = gson.toJson(json);
If you use FileWriter you can simple use that string to write to a file.

How to retrieve images from server to user using Struts 2

I have a Product entity, which has a imageUrl String field.
Products images after obtaining from user will be saved in directory:
System.getProperty("user.home") + "shop/data/product/"
And when user wants to see some Product I need to get this image from "user.home"+... to JSP page.
I've tried to read the image into the byte array, convert it to Base64 encoding, and then refer in JSP like this:
<img alt="image from user home" src="data:image/png, base64;${requestScope.image}">
But this solution is not working, and as far as I understand, it has a limitation on image size.
Could you suggest me a way how to do such thing?
Try this ( i think you have some typo )
<img alt="image from user home" src="data:image/png;base64,${requestScope.image}">
Also use this site: http://www.askapache.com/online-tools/base64-image-converter/ to make sure that your output Base64 code is correct.
There's an example of ImageAction that serves image from the file system. It's called
Struts 2 dynamic image example.
Instead of base64 encoding/decoding which increases the content length two times and slows down page loading you can use the action that returnes image bytes from the file. It could be a database, in this way it should fetch bytes from Blob.
In your <img> tag that is using src attribute can contain the URL to the action that returns response with a header Content-Type: image/jpeg and bytes written to the body.
This is the code of the ImageAction:
#Result(type = "stream", params = {"contentType", "${type}"})
public class ImageAction extends ActionSupport implements ServletRequestAware {
byte[] imageInByte = null;
String imageId;
private HttpServletRequest servletRequest;
private final static String type = "image/jpeg";
public getInputStream() { return new ByteArrayInputStream(getCustomImageInBytes()); }
public String getType() { return type; }
private String getFilename() {
return this.filename;
}
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
public ImageAction() {
System.out.println("ImageAction");
}
public byte[] getCustomImageInBytes() {
System.out.println("imageId" + imageId);
BufferedImage originalImage;
try {
originalImage = ImageIO.read(getImageFile(this.imageId));
// convert BufferedImage to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "jpeg", baos);
baos.flush();
imageInByte = baos.toByteArray();
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return imageInByte;
}
private File getImageFile(String imageId) {
String filePath = servletRequest.getSession().getServletContext().getRealPath("/");
File file = new File(filePath + "/Image/", imageId);
System.out.println(file.toString());
return file;
}
#Override
public void setServletRequest(HttpServletRequest request) {
this.servletRequest = request;
}
}
This action supposed to have configuration created by convention-plugin. So it could be used in HTML like this
<img src="<s:url action='Image?imageId=darksouls.jpg' />" alt=""/>
So Alireza Fattahi was right that I had mistakes in my code. The first one is typo in img tag (see answer by Alireza Fattahi), the second one is after reading image to bytes array
byte[] image = ...;
I used
Base64.getEncoder().encode(image);
instead of
Base64.getEncoder().encodeToString(image));
So eventually this method with returning Base64 encoded image works. If there is a better choices - please left comments and answers.

Error delivering a csv file through the browser

What my application is doing is creating a large csv file (its a report) and the idea is to deliver the contents of the csv file without actually saving a file for it. Here's my code
String csvData; //this is the string that contains the csv contents
byte[] csvContents = csvData.getBytes();
response.contentType = "text/csv";
response.headers.put("Content-Disposition", new Header(
"Content-Disposition", "attachment;" + "test.csv"));
response.headers.put("Cache-Control", new Header("Cache-Control",
"max-age=0"));
response.out.write(csvContents);
ok();
The csv files that are being generated are rather large and the error i am getting is
org.jboss.netty.handler.codec.frame.TooLongFrameException: An HTTP line is larger than 4096 bytes.
Whats the best way to overcome this issue?
My tech stack is java 6 with play framework 1.2.5.
Note: the origin of the response object is play.mvc.Controller.response
Please use
ServletOutputStream
like
String csvData; //this is the string that contains the csv contents
byte[] csvContents = csvData.getBytes();
ServletOutputStream sos = response.getOutputStream();
response.setContentType("text/csv");
response.setHeader("Content-Disposition", "attachment; filename=test.csv");
sos.write(csvContents);
We use this to show the results of an action directly in the browser,
window.location='data:text/csv;charset=utf8,' + encodeURIComponent(your-csv-data);
I am not sure about the out of memory error but I would at least try this:
request.format = "csv";
renderBinary(new ByteArrayInputStream(csvContents));
Apparently netty complains that the http-header is too long - maybe it somehow thinks that your file is part of the header, see also
http://lists.jboss.org/pipermail/netty-users/2010-November/003596.html
as nylund states, using renderBinary should do the trick.
We use writeChunk oursleves to output large reports on the fly, like:
Controller:
public static void getReport() {
final Report report = new Report(code, from, to );
try {
while (report.hasMoreData()) {
final String data = await(report.getData());
response.writeChunk(data);
}
} catch (final Exception e) {
final Throwable cause = e.getCause();
if (cause != null && cause.getMessage().contains("HTTP output stream closed")) {
logger.warn(e, "user cancelled download");
} else {
logger.error(e, "error retrieving data");
}
}
}
in report code
public class Report {
public Report(final String code, final Date from, final Date to) {
}
public boolean hasMoreData() {
// find out if there is more data
}
public Future<String> getData() {
final Job<String> queryJob = new Job<String>() {
#Override
public String doJobWithResult() throws Exception {
// grab data (e.g read form db) and return it
return data;
}
};
return queryJob.now();
}
}

Categories