aboutsummaryrefslogtreecommitdiffstats
path: root/documentation/articles/LettingTheUserDownloadAFile.asciidoc
blob: 1ede8bd5c09bedf505304b280523b8b92be67dfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
---
title: Letting The User Download A File
order: 6
layout: page
---

[[letting-the-user-download-a-file]]
= Letting The User Download A File

Providing a file for download to the user might be trickier that what it
seems - the file should be downloaded instead of just opened by the
browser, download blockers should be avoided, a unique URL should be
generated and server-side memory should be released when the file is no
longer available for download. All this is taken care of by the new
`FileDownloader` extension that can make almost any component start a
download when clicked.

[source,java]
....
public class LettingUserDownladFile extends UI {

    @Override
    protected void init(VaadinRequest request) {
        Button downloadButton = new Button("Download image");

        StreamResource myResource = createResource();
        FileDownloader fileDownloader = new FileDownloader(myResource);
        fileDownloader.extend(downloadButton);

        setContent(downloadButton);
    }

    private StreamResource createResource() {
        return new StreamResource(new StreamSource() {
            @Override
            public InputStream getStream() {
                String text = "My image";

                BufferedImage bi = new BufferedImage(100, 30, BufferedImage.TYPE_3BYTE_BGR);
                bi.getGraphics().drawChars(text.toCharArray(), 0, text.length(), 10, 20);

                try {
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    ImageIO.write(bi, "png", bos);
                    return new ByteArrayInputStream(bos.toByteArray());
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }

            }
        }, "myImage.png");
    }
}
....

To use `FileDownloader`, you just create an instance of the extension
and use it to extend the component or `MenuItem` that should start the download. You
should also note that `FileDownloader` works best with resources that
are served by Vaadin as it relies on sending some special HTTP headers
along with the file to ensure the browser doesn't try to open the file
even if it's is a file type that the browser knows how to deal with.

[[lazily-determine-the-content-and-the-name-of-the-file-being-server]]
==== Lazily determine the content and the name of the file being server

One can lazily determine the content of the file using a
`StreamResource`. Yet the name of the file that is going to be
downloaded has to be known at creation time of the `FileDownloader`. It
seems that a way around this, is in fact missing from Vaadin 7 as of
now.

A possible solution is to subclass `FileDownloader` and set the name right
before the download happens:

[source,java]
....
/**
 * This specializes {@link FileDownloader} in a way, such that both the file name and content can be determined
 * on-demand, i.e. when the user has clicked the component.
 */
public class OnDemandFileDownloader extends FileDownloader {

  /**
   * Provide both the {@link StreamSource} and the filename in an on-demand way.
   */
  public interface OnDemandStreamResource extends StreamSource {
    String getFilename ();
  }

  private static final long serialVersionUID = 1L;
  private final OnDemandStreamResource onDemandStreamResource;

  public OnDemandFileDownloader (OnDemandStreamResource onDemandStreamResource) {
    super(new StreamResource(onDemandStreamResource, ""));
    this.onDemandStreamResource = checkNotNull(onDemandStreamResource,
      "The given on-demand stream resource may never be null!");
  }

  @Override
  public boolean handleConnectorRequest (VaadinRequest request, VaadinResponse response, String path)
      throws IOException {
    getResource().setFilename(onDemandStreamResource.getFilename());
    return super.handleConnectorRequest(request, response, path);
  }

  private StreamResource getResource() {
    StreamResource result = null;
    this.getSession().lock();
    try {
      result = (StreamResource) this.getResource("dl");
    } finally {
      this.getSession().unlock();
    }
    return result;
  }
}
....

[[lazily-determine-the-content-and-the-name-of-the-file-being-server]]
==== Cancelled downloads

Since downloadable files may be quite big, and the download process may take time, the user might decide to
cancel the download process. In this case `IOException` may be thrown by the web server. That
does not mean something went wrong with the application, but the user pressed `Cancel` button during download. To prevent the exception to be logged, you can catch and ignore it as here:

```java
public class IgnoreCancelDownloader extends FileDownloader {

    ...

    @Override
    public boolean handleConnectorRequest(final VaadinRequest request, final VaadinResponse response, final String path) {
        try {
            return super.handleConnectorRequest(request, response, path);
        } catch (final IOException ignored) {
            return true;
        }
    }
}

```
Note that the exception is a sublclass of `IOException`, but the particular class depends on the web container.