I’m currently developing a Vaadin application. The function I’m working on is creating two PDF’s, merging them and then downloading it to the client as a temporary file. Just a few times i get this strange IOException with no reference to my code. Any idea on what may cause it?
The stack trace:
java.io.IOException: Stream Closed
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(Unknown Source)
at com.vaadin.server.DownloadStream.writeResponse(DownloadStream.java:304)
at com.vaadin.server.AbstractClientConnector.handleConnectorRequest(AbstractClientConnector.java:646)
at com.vaadin.server.ConnectorResourceHandler.handleRequest(ConnectorResourceHandler.java:83)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1408)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:350)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:587)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:52)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Unknown Source)
Yeah, your code just creates the FileInputStream in the StreamSource, after which the framework just reads from the stream.
It’s hard to say what could cause the problem. I hope there’s nothing special in your server setup that could damage the stream, like load-balancing, redeployment, or something.
I don’t know what tools you are using for merging the PDFs, but at least some tools (like pdftk) allow writing to stdout, which you could serve as a PipedInputStream or something.
It could be a problem with the setup for the server, because I don’t seem to be able to reproduse the error on my local setup.
I have been looking a bit deeper in this problem and have found out that the error also appear in another function that uses JasperReports and fetching of pdf-files. I think that the problem may come from a class I call
TemporaryFileDownloadResource. Here are the code for generating a temporary file and downloading it to the client.
Generating the temporary file:
private void fillReport(List<?> datasource, String templateName) {
byte buffer = new FileReader(templateName).read();
if (buffer == null) {
// running from intellij? Then try relative path for file
buffer = new FileReader(templateName.replace("classpath://", "classpath:///")).read();
}
if (buffer == null) {
return; //just give up...
}
// Now all should be ok... byte has content
File tempFile;
try {
tempFile = File.createTempFile("tmp", ".pdf");
tempFile.deleteOnExit();
// Now all should be ok... byte has content
JasperReport jasperReport = (JasperReport) JRLoader.loadObject(new ByteArrayInputStream(buffer));
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, new HashMap<>(),
new JRBeanCollectionDataSource(datasource));
JasperExportManager.exportReportToPdfFile(jasperPrint, tempFile.getPath());
} catch (IOException | JRException e) {
logger.error(e.getMessage(), e);
return;
}
// Show the produced label in a window. Let the user view and print the label
String downloadFileName = System.currentTimeMillis() + "_filename.pdf";
String contentType = "application/pdf";
TemporaryFileDownloadResource resource;
try {
resource = new TemporaryFileDownloadResource(downloadFileName, contentType, tempFile);
resource.setMIMEType("application/pdf");
//Create window
Window window = new Window();
window.setResizable(true);
window.setCaption(WorkstationTexts.get("labelPrintViewer.label.caption"));
window.setWidth("800");
window.setHeight("600");
window.center();
StreamResource r = new StreamResource(resource.getStreamSource(), "label.pdf");
r.setMIMEType("application/pdf");
r.setCacheTime(0); // no caching of this. Get fresh copy
BrowserFrame frame = new BrowserFrame(WorkstationTexts.get("labelPrintViewer.label.caption"), r);
frame.setSizeFull();
window.setContent(frame);
UI.getCurrent().addWindow(window);
} catch (FileNotFoundException e) {
logger.error(e.getMessage(), e);
}
}
Here are the class that creates the downloaded file on the client:
public class TemporaryFileDownloadResource extends StreamResource {
/**
* The filename.
*/
private final String filename;
/**
* The content type.
*/
private String contentType;
/**
* Instantiates a new temporary file download resource.
*
* @param fileName the file name
* @param contentType the content type
* @param tempFile the temp file
* @throws FileNotFoundException the file not found exception
*/
public TemporaryFileDownloadResource(final String fileName,
final String contentType, final File tempFile) throws FileNotFoundException {
super(new FileStreamResource(tempFile), fileName);
this.filename = fileName;
this.contentType = contentType;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.StreamResource#getStream()
*/
@Override
public DownloadStream getStream() {
final DownloadStream stream =
new DownloadStream(getStreamSource().getStream(), contentType, filename);
stream.setParameter("Content-Disposition", "attachment;filename=" + filename);
// This magic incantation should prevent anyone from caching the data
stream.setParameter("Cache-Control", "private,no-cache,no-store");
// In theory <=0 disables caching. In practice Chrome, Safari (and, apparently, IE) all
// ignore <=0. Set to 1s
stream.setCacheTime(1000);
return stream;
}
/**
* The Class FileStreamResource.
*/
private static class FileStreamResource implements StreamResource.StreamSource {
/**
* The Constant serialVersionUID.
*/
private static final long serialVersionUID = 3801605481686085335L;
/**
* The input stream.
*/
private final InputStream inputStream;
/**
* Instantiates a new file stream resource.
*
* @param fileToDownload the file to download
* @throws FileNotFoundException the file not found exception
*/
public FileStreamResource(final File fileToDownload) throws FileNotFoundException {
inputStream = new DeletingFileInputStream(fileToDownload);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.StreamResource.StreamSource#getStream()
*/
@Override
public InputStream getStream() {
return inputStream;
}
}
}
class DeletingFileInputStream extends FileInputStream implements Serializable {
/**
* The file.
*/
protected File file = null;
/**
* Instantiates a new deleting file input stream.
*
* @param file the file
* @throws FileNotFoundException the file not found exception
*/
public DeletingFileInputStream(final File file) throws FileNotFoundException {
super(file);
this.file = file;
}
/*
* (non-Javadoc)
*
* @see java.io.FileInputStream#close()
*/
@Override
public void close() throws IOException {
super.close();
file.delete();
}
}