Issue closing modal window after downloading created PDF using URI handler

I am trying to download a dynamically created PDF (iText generated) from my Vaadin application.
My application seem to get out of sync when opening the PDF-url in a new (browser) window.

The scenario:

  • A Vaadin URI handler intercepts a specific url to create a PDF-file (using iText) and returns a binary stream with all the appropriate html headers.
  • To download the PDF from my Vaadin application (same as the one that handles the PDF URI) I open a new browser window and set the url to the PDF URI. I manage to get the browser to download the pdf and ask if I want to save or open the file. (The new browser window never really appears since the url downloads a binary file.)
  • The download action in my application is triggered by the user through a button (“create pdf”) in a modal window (“create report dialog”).

The problem is that I can not find a programmatic way to close the modal dialog/window (“create report dialog”) after the pdf-file is downloaded (to the new browser window). The modal dialog is still visible and my application says that it is out of sync.

If I comment out the actual opening of the new browser window my I manage to close the modal dialog programatically. I have always managed to close the modal dialog using the either the “x” button or my own “Close” button without any problem (unless I tried to do it programmatically first).

I have tried to close the modal window before i open the new browser download window and vice versa, with no luck.

I have tried a lot of different code configurations and alternative methods, all with no luck. Does anyone have an idea what my problem might be?

I don’t know how you create your PDF. Here is the code I use to allow people to download an Excel, which relies on Vaadin StreamResource to deal with all the ugly mess. Same should work with PDFs or other files.

Note that there is some kludgery to deal with IE, what else could be expected.

StreamResource streamResource = new StreamResource(streamSource, filename+".xls", this);
streamResource.setCacheTime(5000); // no cache (<=0) does not work with IE8
streamResource.setMIMEType("application/x-msexcel");
this.getMainWindow().open(streamResource,"_top");

In the above code, streamSource contains the bytes. I suspect your package creates a OutputStream, but StreamResource wants an InputStream. In order to convert, you can use PipedInputStream in = new PipedInputStream(); final PipedOutputStream out = new PipedOutputStream(in);
You write on “out”, and you create your Vaadin StreamResource with “in”

Do you use “_self” as the target for the new window? This might be relevant:
#3558

Thank you for your quick reply. It really helped out a lot. :slight_smile:
I did not use the piped stream advice since I am not using inter-thread communication but rather one single execution thread.
I thought I would post my final solution here, it might help someone else out there.

Do note that I did not want the browser to open the pdf file using a browser plugin but rather ask whether to download the file or open the file. Thus, I use the MIME type “binary-octet” and set the “attachment” value for “content-disposition” (a fix for IE).
If you would like the pdf file to open inside the browser (and most likely replace your Vaadin application) you should change the MIME type to appropriate value and comment out the line with setting of “content-disposition” parameter to “attachment”. To open the pdf in the another browser window (or browser tab) than your Vaadin application you can probably also change the “_top” value to either “_blank” or any other unique name for the new window.

The code open download binary data that have been dynamically created (i.e. not a file stored somewhere on disc).

public void downloadReport(final ReportParameters reportParameters, final Component component) {
  final byte[] pdfData = createPdfReport(reportParameters);
	
  final String pdfFileName = getPdfFileName(reportParameters);

  // Set MIME type to binary data to prevent opening of pdf in browser window
  final DynamicStreamResource streamResource = 
    new DynamicStreamResource(pdfData, pdfFileName, 
      [color=#5F0000]
[b]
DynamicStreamResource.MIME_TYPE_BINARY_DATA
[/b]
[/color], component.getApplication());

  streamResource.setCacheTime(5 * 1000); // no cache (<=0) does not work with IE8, (in milliseconds)
  component.getApplication().getMainWindow().open(streamResource, "[color=#5F0000]
[b]
_top
[/b]
[/color]");
}

I have hidden some implementation in the class DynamicStreamResource.

package com.acme.download;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import com.vaadin.Application;
import com.vaadin.terminal.DownloadStream;
import com.vaadin.terminal.StreamResource;

public class DynamicStreamResource extends StreamResource {

  private static final long serialVersionUID = -4304057799149311779L;
  
  public static final String MIME_TYPE_BINARY_DATA = "application/octet-stream";
  public static final String MIME_TYPE_PDF = "application/pdf";

  private final byte[] binaryData;

  private final String filename;

  public DynamicStreamResource(final byte[] binaryData, final String filename, 
        final String mimeType, final Application application) {
    super(new StreamSource() {

      @Override
      public InputStream getStream() {
        return new ByteArrayInputStream(binaryData);
      }
    }, filename, application);
    
    this.binaryData = binaryData;
    this.filename = filename;
    
    setMIMEType(mimeType);
  }
  
  @Override
  public DownloadStream getStream() {
    final DownloadStream downloadStream = super.getStream();

    // Set the "attachment" to force save-dialog. Important for IE7 (and probably IE8)
    [color=#5F0000]
[b]
downloadStream.setParameter("Content-Disposition", "attachment; filename=\"" + filename + "\"");
[/b]
[/color]

    // Enable deterministic progressbar for download
    [color=#5F0000]
[b]
downloadStream.setParameter("Content-Length", Integer.toString(binaryData.length));
[/b]
[/color]
    
    return downloadStream;
  }

}

The Piped stream solution comes in handy if you cannot afford to keep all the data in memory, which is the one drawback to your class. If the data does not fit, or if you need to scale to many users where the cumulative amount of data might not fit, the trick is to write to a stream, and have another thread pump it to the client. Threads are actually cheap in Java.

Christian,

I have the exact need you do, to have a dynamically generated file sent to the user for download rather than saving to the server.

I am trying to use the code you posted but an getting a NullPointerException on line 23 of DynamicStreamResource… I am using your exact code calling it from within a class that extends VerticalLayout, I am passing a GridLayout as the component required, all other variables are not null.

Any ideas on what’s wrong?
Here’s my code… right now i’m filling my excel with dummy data, not using the beans to load actual data on it.

public class ExcelReport extends VerticalLayout implements TPTView{
    private static final long serialVersionUID = -5162503344338525054L;
        private ReportResult reportResult; //Object that contain the report result
        private ReportConfigurationBean reportConfigurationBean; //the configuration of the executed report
        private Table notesTable = new Table();  //table to show notes information
        private Table resultDateTable = new Table();
        GridLayout grid = new GridLayout(5, 5);

    public ExcelReport(ReportResult pReportResult,ReportConfigurationBean pReportConfigurationBean) {
        super();
        reportResult = pReportResult;
        reportConfigurationBean = pReportConfigurationBean;

        this.setImmediate(true);
        this.setDebugId("ExcelExportVLayout");
        this.setHeight(null);
        this.setWidth(null);

        //Create Excel file
        HSSFWorkbook wb = new HSSFWorkbook();
        HSSFSheet sheet = wb.createSheet();

        for(int i=0;i<50;i++){
            HSSFRow row = sheet.createRow(i);
            for(int j=0; j<10;j++){
                HSSFCell cell = row.createCell(j);
                cell.setCellValue(i*j*1.7);
            }
        }

         downloadReport(wb.getBytes(), "D:\\DownloadExcel.xls", grid);

     }

public void downloadReport(final byte[] pData, final String pFileName, Component pComponent) {
 final DynamicStreamResource streamResource =
  new DynamicStreamResource(pData, pFileName,
  DynamicStreamResource.MIME_TYPE_EXCEL, pComponent.getApplication() );

  streamResource.setCacheTime(5 * 1000); // no cache (<=0) does not work with IE8, (in milliseconds)
  getApplication().getMainWindow().open(streamResource, "_top");
}

I am not sure what I was doing, I guess I was too tired. I took a couple of hours to clear my mind, started from scratch and it is working!

inspired by anderson’s code, my alternative class for dynamic download:

public class DownloadStreamResource extends StreamResource
{
public DownloadStreamResource(StreamSource streamSource, String filename, Application application)
{
super(streamSource, filename, application);
setMIMEType(“application/octet-stream”);
}

public DownloadStreamResource(final byte[] data,  String filename, Application application)
{
	this(new StreamSource()
	{
		@Override
		public InputStream getStream()
		{
			return new ByteArrayInputStream(data);
		}
	}, filename, application);
}

public void setBinaryData(final byte[] data)
{
	this.setStreamSource(new StreamSource()
	{
		@Override
		public InputStream getStream()
		{
			return new ByteArrayInputStream(data);
		}
	});
}

@Override
public DownloadStream getStream()
{
	final DownloadStream ds = super.getStream();
	if(ds!=null)
	{
		ds.setParameter("Content-Disposition", "attachment; filename=\"" + getFilename() + "\"");
		try
		{
			ds.setParameter("Content-Length", Integer.toString(ds.getStream().available()));
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	return ds;
}

}

Hello all,

I am getting mixed results using the example Christian Andersson provided. It seems to work well in IE9, Chrome, Firefox, and Opera. On IE8 and Safari, it acts as if nothing happens. There is no warning message about popup windows attempting to open or anything. I found on Safari if I enable popup windows, it works fine.

My question is, if the download of the file (my file is a GZIP file) fails because of a popup blocker, is there any way to get notified of this failure? If the download is unsuccessful, I would like to display a message to the user to disable popup blockers in order to download the file. It seems the file opening doesn’t happen until a paint request occurs, and I can’t find any way to be notified when the paint fails.

Thanks in advance,
David

What about opening it in the same window, not in a popup, as is done
in this example
? For me it at least just opens the document, but I don’t know how it works in other than Firefox and in differents OSs. It can be a problem though if the browser decides to moves out of Vaadin application in some situation.

I don’t think there is a trivial way to get a notification, but you could make the getStream() register that the resource was read and then check that after a short delay in the application window (polling needed). You could have some message like “Waiting for document display to start. Please allow popups if necessary.”

I’m trying to do like the original poster and have it download the file instead of trying to open the file. When I tried using a target of “_top” instead of “_blank”, it cleared the screen, but the download of the file never actually happened (was never prompted for it). Opening in a different target was the only way I could get it to actually deliver the file to the client.

Please pardon my ignorance, but how do I register that the resource was read? I don’t see any callbacks to that effect on the DownloadStream class.

Thanks,
David

Ask a question: when will I use the ExcelExport derived data, an exception is thrown.the file is not create.
六月 02, 2015 5:09:10 下午 com.vaadin.addon.tableexport.ExcelExport sendConverted
警告: Converting to XLS failed with IOException java.io.IOException: 没有那个文件或目录
六月 02, 2015 5:09:10 下午 com.vaadin.server.DefaultErrorHandler doDefault
严重:
java.lang.NullPointerException
at com.vaadin.addon.tableexport.ExcelExport.sendConverted(ExcelExport.java:384)
at com.vaadin.addon.tableexport.TableExport.export(TableExport.java:68)
期待回复。

i am not able to open window browser option to save my file on local system.