I want my web application users to download some data as an Excel file.
I have the next function to send an Input Stream in the response object.
public static void sendFile(InputStream is, HttpServletResponse response) throws IOException {
BufferedInputStream in = null;
try {
int count;
byte[] buffer = new byte[BUFFER_SIZE];
in = new BufferedInputStream(is);
ServletOutputStream out = response.getOutputStream();
while(-1 != (count = in.read(buffer)))
out.write(buffer, 0, count);
out.flush();
} catch (IOException ioe) {
System.err.println("IOException in Download::sendFile");
ioe.printStackTrace();
} finally {
if (in != null) {
try { in.close();
} catch (IOException ioe) { ioe.printStackTrace(); }
}
}
}
I would like to transform my HSSFWorkbook Object to an input stream and pass it to the previous method.
public InputStream generateApplicationsExcel() {
HSSFWorkbook wb = new HSSFWorkbook();
// Populate the excel object
return null; // TODO. return the wb as InputStream
}
http://poi.apache.org/apidocs/org/apache/poi/hssf/usermodel/HSSFWorkbook.html
The problem with your question is that you are mixing OutputStreams and InputStreams. An InputStream is something you read from and an OutputStream is something you write to.
This is how I write a POI object to the output stream.
// this part is important to let the browser know what you're sending
response.setContentType("application/vnd.ms-excel");
// the next two lines make the report a downloadable file;
// leave this out if you want IE to show the file in the browser window
String fileName = "Blah_Report.xls";
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// get the workbook from wherever
HSSFWorkbook wb = getWorkbook();
OutputStream out = response.getOutputStream();
try {
wb.write(out);
}
catch (IOException ioe) {
// if this happens there is probably no way to report the error to the user
if (!response.isCommited()) {
response.setContentType("text/html");
// show response text now
}
}
If you wanted to re-use your existing code you'd have to store the POI data somewhere then turn THAT into an input stream. That'd be easily done by writing it to a ByteArrayOutputStream, then reading those bytes using a ByteArrayInputStream, but I wouldn't recommend it. Your existing method would be more useful as a generic Pipe implementation, where you can pipe the data from an InputStream to and OutputStream, but you don't need it for writing POI objects.
you can create a InputStream from a object.
public InputStream generateApplicationsExcel() {
HSSFWorkbook wb = new HSSFWorkbook();
// Populate a InputStream from the excel object
return new ByteArrayInputStream(excelFile.getBytes());
}
My solution is to transfer the HSSFWorkbook to ByteArrayOutputStream first, and then create an InputStream from ByteArrayOutputStream :
HSSFWorkbook wb = ...
// Fill an empty output stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wb.write(baos);
// Close the document
wb.close();
// Create the input stream (do not forget to close the inputStream after use)
InputStream is = new ByteArrayInputStream(baos.toByteArray());
I think I understand what you're trying to do (maybe I am undershooting, though)
you don't really need that much code - check out the write method -
HSSFWorkbook wb = new HSSFWorkBook();
//populate
ServletOutputStream out = response.getOutputStream();
try {
wb.write(out);
out.flush();
}
catch (IOException ioe) {
//whatever
}
out.close();
As far as I remember when I worked w/ POI that's what I did. If you're inside a web framework you may have to finaggle it so that the framework doesn't try to do something with the that ServletOutputStream after you've closed it. If it tries, you'll get an exception throwing telling you that the output stream is closed already.
Related
I find a problem on using apache-poi. Here is my code.
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(request.getSession().getServletContext().getRealPath("/Testing.xlsx")));
XSSFSheet spreadsheet = workbook.getSheet("TempSheet");
File template = new File(request.getSession().getServletContext().getRealPath("/Testing.xlsx"));
FileOutputStream out = new FileOutputStream(template);
workbook.write(out);
out.close();
String currentTime = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String fileName = "TestingABC.xlsx";
String absoluteDiskPath = request.getSession().getServletContext().getRealPath("/Testing.xlsx");
File f = new File(absoluteDiskPath);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
InputStream in = new FileInputStream(f);
ServletOutputStream outs = response.getOutputStream();
try {
byte[] outputByte = new byte[4096];
while (in.read(outputByte, 0, 4096) != -1) {
outs.write(outputByte, 0, 4096);
}
outs.flush();
outs.close();
in.close();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
if(outs != null)
outs.close();
if(in != null)
in.close();
}catch (Exception ioe2) {
ioe2.printStackTrace();
}
}
}
Excel file is download normally, but when i open the file, the following error message display :
"When i try this, i receive the message: "We found a problem with some content in 'TestingABC.xlsx'. Do you want us to try to recover as much as we can? If you trust the source of this workbook, click 'Yes'"
After i click 'Yes'
the error is as follow:
"Excel completed file level validation and repair. Some parts of this workbook may have been repaired or discarded."
Actually, the file data is correct, but how can i remove this message ?
Thanks.
It work after adding content Length:
response.setContentLength((int) file.length());
I'm trying to dynamically zip my files as a response for the user, but for some reason they become slightly corrupted on the way. The client can receive them, open the zip folder and browse the files. However, opening or extracting them won't work.
Here's my code:
private void dynamicallyZipFiles(IHTTPSession session) {
try {
// Let's send the headers first
OutputStream os = session.getOutputStream();
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")), false);
pw.append("HTTP/1.1 200 \r\n");
printHeader(pw, "Connection", "keep-alive");
printHeader(pw, "Content-Type", "application/zip, application/octet-stream");
printHeader(pw, "Content-Disposition", "attachment; filename=\"pack.zip\"");
printHeader(pw, "Transfer-Encoding", "chunked");
pw.append("\r\n");
pw.flush();
// Send all the files from the list of files
ChunkedOutputStream cos = new ChunkedOutputStream(os);
ZipOutputStream zos = new ZipOutputStream(cos);
final LinkedList<String> files = new LinkedList<String>();
files.add("file1.txt");
files.add("file2.txt");
while (!files.isEmpty()) {
String file = files.remove();
File toBeSent = new File(file);
try {
ZipEntry ze = new ZipEntry(file);
zos.putNextEntry(ze);
InputStream is = (InputStream) new FileInputStream(toBeSent);
long BUFFER_SIZE = 16 * 1024;
byte[] buff = new byte[(int) BUFFER_SIZE];
int len;
while ((len = is.read(buff)) > 0) {
zos.write(buff, 0, len);
}
is.close();
zos.flush();
cos.flush();
zos.closeEntry();
} catch (Exception e) {
e.printStackTrace();
}
}
// Files have been sent, send the closing chunk
//cos.write("0\r\n\r\n".getBytes(), 0, "0\r\n\r\n".getBytes().length);
// The above line of code was the problem! Without it, it works!
cos.flush();
zos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// Helper for printing headers
private void printHeader(PrintWriter pw, String key, String value) {
pw.append(key).append(": ").append(value).append("\r\n");
}
I've been struggling for quite some hours already with this. I believe it has to have something to do with the closing chunk or with the closing of the connection?
I'm not sure what's the right order or way to close streams. I believe that when you have kind of "layered" streams and you close the top most one it will automatically close all the ones below too? And how about flush, if you flush a stream, will it flush the streams below it as well?With this I mean that my ZipStream for example, if I close it, will it close all the other streams as well? And if I flush it, will it flush all the other streams as well again?
I have the following problem: I have an HttpServlet that create a file and return it to the user that have to receive it as a download
byte[] byteArray = allegato.getFile();
InputStream is = new ByteArrayInputStream(byteArray);
Base64InputStream base64InputStream = new Base64InputStream(is);
int chunk = 1024;
byte[] buffer = new byte[chunk];
int bytesRead = -1;
OutputStream out = new ByteArrayOutputStream();
while ((bytesRead = base64InputStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
As you can see I have a byteArray object that is an array of bytes (byte[] byteArray) and I convert it into a file in this way:
First I convert it into an InputStream object.
Then I convert the InputStream object into a Base64InputStream.
Finally I write this Base64InputStream on a ByteArrayOutputStream object (the OutputStream out object).
I think that up to here it should be ok (is it ok or am I missing something in the file creation?)
Now my servlet have to return this file as a dowload (so the user have to receive the download into the browser).
So what have I to do to obtain this behavior? I think that I have to put this OutputStream object into the Servlet response, something like:
ServletOutputStream stream = res.getOutputStream();
But I have no idea about how exactly do it? Have I also to set a specific MIME type for the file?
It's pretty easy to do.
byte[] byteArray = //your byte array
response.setContentType("YOUR CONTENT TYPE HERE");
response.setHeader("Content-Disposition", "filename=\"THE FILE NAME\"");
response.setContentLength(byteArray.length);
OutputStream os = response.getOutputStream();
try {
os.write(byteArray , 0, byteArray.length);
} catch (Exception excp) {
//handle error
} finally {
os.close();
}
EDIT:
I've noticed that you are first decoding your data from base64, the you should do the following:
OutputStream os = response.getOutputStream();
byte[] buffer = new byte[chunk];
int bytesRead = -1;
while ((bytesRead = base64InputStream.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
You do not need the intermediate ByteArrayOutputStream
With org.apache.commons.compress.utils.IOUtils you can just "copy" from one file or stream (e.g. your base64InputStream) to the output stream:
response.setContentType([your file mime type]);
IOUtils.copy(base64InputStream, response.getOutputStream());
response.setStatus(HttpServletResponse.SC_OK);
You'll find that class here https://mvnrepository.com/artifact/org.apache.commons/commons-compress
A similar class (also named IOUtils) is also in Apache Commons IO (https://mvnrepository.com/artifact/commons-io/commons-io).
does someone have an idea why this code to create a gzipped string is not working? CLI gzip on a Mac can't open the resulting file: "Not in gz format".
Please note: I need the string, not the file. Creating the gzipped file directly works, so does writing the JSON without zipping it.
The file writing in this example is just for testing purposes.
public someMethod {
String gzippedString = this.gzippedString(finalJSONObject.toJSONString());
OutputStream outputStream = new FileOutputStream(new File(this.jsonOutputPath + "/myfile.gz"));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.append(gzippedString);
writer.close();
}
private String gzippedString(String inputString) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
gzipOutputStream.write(inputString.getBytes());
gzipOutputStream.close();
outputStream.close();
String gzippedString = outputStream.toString();
return gzippedString;
}
EDIT:
chrylis showed me the way:
public void someMethod() {
byte[] byteArray = this.gzippedByteArray(finalJSONObject.toJSONString());
FileOutputStream out = new FileOutputStream(this.jsonOutputPath + "/myfile.gz");
out.write(byteArray);
out.close();
}
private byte[] gzippedByteArray(String inputString) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
gzipOutputStream.write(inputString.getBytes());
gzipOutputStream.close();
outputStream.close();
byte[] gzippedByteArray = outputStream.toByteArray();
return gzippedByteArray;
}
This results in a working gzipped JSON.
Thanks a lot!
You're round-tripping binary data through a String, which has a character encoding and other such mangling. Use the byte[] directly instead.
I need to read a file (that is not available on web) on the server and output it to the user as a downloadable file.
The scenario is
The user click a link from an XPage
The request is sent to the server which reads a predefined file in the server file system
The file is brought back to the user as a downloadable file in the webbrowser.
The file on the server can be in any format, e.g .pdf, .exe, .doc etc
It does not matter if this is done on SSJS or in java.
I would really appreicate some code
Here is a similar question:
How to stream file from xPages?
And here is part of the Java code taken from there and completed by me (+a fix from you!). I have now tested it also and it works:
FacesContext facesContext = FacesContext.getCurrentInstance();
XspHttpServletResponse response = (XspHttpServletResponse) facesContext.getExternalContext().getResponse();
String strFileName = "myfile.txt";
String strFilePath= "c:" + File.separator + strFileName;
response.setContentType(URLConnection.guessContentTypeFromName(strFileName));
response.setHeader("Content-Disposition","attachment;filename=" + strFileName);
//File file = new File(strFilePath);
FileInputStream fileIn = new FileInputStream(strFilePath);
ServletOutputStream out = response.getOutputStream();
int iLen = 0;
byte[] btBuffer = new byte[10240]; // Not sure about optimal buffer size
while ((iLen = fileIn.read(btBuffer)) != -1) {
out.write(btBuffer, 0, iLen);
}
facesContext.responseComplete();
out.close();
You could do all this in SSJS also.
If guessContentTypeFromName does not guess it then you need to modify the definition file on server. Or if you have a limited set of file types you can place the MIME-type table in your code/application.
Here is the code I came up with to do this, def not production code.
public static byte[] grabFile(String readFile) throws IOException {
File file = new File(readFile);
ByteArrayOutputStream ous = new ByteArrayOutputStream();
InputStream ios = new FileInputStream(file);
try {
byte []buffer = new byte[4096];
int read = 0;
while ( (read = ios.read(buffer)) != -1 ) {
ous.write(buffer, 0, read);
}
} finally {
try {
if ( ous != null )
ous.close();
} catch ( IOException e) {
}
try {
if ( ios != null )
ios.close();
} catch ( IOException e) {
}
}
return ous.toByteArray();
}
public static void download() throws IOException {
byte[] data = grabFile("\\\\server\\path\\to\\file.pdf");
HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
response.reset();
response.setContentType("application/pdf");
response.setHeader("Content-disposition", "attachment; filename=\"filename.pdf\"");
OutputStream output = response.getOutputStream();
output.write(data);
output.close();
FacesContext.getCurrentInstance().responseComplete();
}
Then just call the download method from the beforeRenderResponse of your Xpage