• No results found

Handling file uploads

In document Tapestry in Action (Page 191-196)

Advanced form components

4.5 Handling file uploads

A common feature of many web applications is support for file uploads, the abil- ity to transfer a file from the user’s computer to the application server as part of a form submission. HTML includes another form control for this purpose, <input type="file">. When a web browser submits a request with a file upload, the nor-

mal encoding used to express the data (the Multipurpose Internet Mail Exten- sions [MIME] type application/x-www-form-urlencoded) is not used. Instead, a

different encoding, multipart/form-data, is used for the upload.

This alternate encoding allows any number of file uploads, interspersed with normal query parameter values (from the other form controls), to be sent from the client to the server in one large binary stream. This difference in encoding should not be any more of an issue to a servlet application developer than the difference between the HTTPGET and PUT requests. Alas, the Servlet API does not include the ability to parse and interpret multipart/form-data content,

which makes handling uploaded files an uphill battle.

Tapestry makes use of the Jakarta commons-fileupload library to seamlessly support forms that contain a mix of ordinary form elements and file uploads. When a form is submitted containing an uploaded file, Tapestry extracts the file content from the request and stores it in memory or in a temporary file (if the file content is large enough).

To the application, an uploaded file is represented by an instance of the inter- face IUploadFile. From this, an application can retrieve the following information:

■ The name of the file (on the client)

■ The complete path of the file (on the client)

■ The MIME content type (as reported by the client)

■ The content of the file, as a java.io.InputStream

The content of the file is deleted at the end of the request; so if the uploaded file is to be used later, it must be stored persistently either to the server’s file system or in a database.

Tapestry’s Upload component makes creating file upload fields as easy as creat- ing ordinary text fields. Figure 4.8 shows a page that uses an Upload component.

It is just as easy to add an Upload component to a Tapestry Form as any other kind of component. The HTML for the Upload component from figure 4.8 is simply

Handling file uploads 161

Including an Upload component inside a Form automatically changes the encod- ing type of the enclosing Form to multipart/form-data. This is another example

of the dynamic nature of Tapestry; you are not responsible for this background detail (selecting the form’s encoding type)—the components automatically do the right thing.

Once the form is submitted and the file is uploaded, the file instance can be passed to another page in the same way as a simple value, such as a string or number. Inside the Upload page class, the listener method does just that:

public abstract IUploadFile getFile(); public void formSubmit(IRequestCycle cycle) {

IUploadFile file = getFile(); if (file == null) return; UploadResults next = (UploadResults) cycle.getPage("UploadResults"); next.setFile(file); cycle.activate(next); }

The second page, UploadResults, displays the data available about the uploaded file, followed by a dump of the contents of the file (in hexadecimal

Figure 4.8 A page containing an Upload component. Clicking the Browse button will open a file selection dialog box.

and ASCII). Figure 4.9 shows an example of what the page looks like after uploading a small file.

Getting the output for the first three fields (name, path, and content type) is straightforward:

<table border="0"> <tr>

<th>Name:</th>

<td><span jwcid="@Insert" value="ognl:file.fileName"> File Name</span></td>

</tr> <tr>

<th>Path:</th>

<td><span jwcid="@Insert" value="ognl:file.filePath"> File Path</span></td>

</tr> <tr>

<th>Content Type:</th>

<td><span jwcid="@Insert" value="ognl:file.contentType"> text/html</span></td>

</tr>

Figure 4.9 After the file is uploaded, the application displays the information available about the uploaded file.

Handling file uploads 163 Getting the output for the binary content is a bit more involved. Tapestry doesn’t have a built-in component for this kind of output, but it does include a utility class, BinaryDumpOutputStream. BinaryDumpOutputStream is a filter that takes as

input a stream of bytes and produces as output the kind of text shown in figure 4.9. The trick is to get this object, which is not a component, integrated into the rendering of the page. Fortunately, as you’ve seen before, components aren’t the only things that can render output in Tapestry. The UploadResults

page class in listing 4.8 includes an inner class that can render the contents of an IUploadFile. package examples.upload; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringWriter; import org.apache.tapestry.ApplicationRuntimeException; import org.apache.tapestry.IMarkupWriter; import org.apache.tapestry.IRender; import org.apache.tapestry.IRequestCycle; import org.apache.tapestry.html.BasePage; import org.apache.tapestry.request.IUploadFile; import org.apache.tapestry.util.io.BinaryDumpOutputStream; public abstract class UploadResults extends BasePage {

public abstract void setFile(IUploadFile file); public abstract IUploadFile getFile();

private static class ContentRenderer implements IRender {

private IUploadFile _file; ContentRenderer(IUploadFile file) {

_file = file; }

public void render(IMarkupWriter writer, IRequestCycle cycle) {

try {

StringWriter buffer = new StringWriter();

Listing 4.8 UploadResults.java: page class for the UploadResults page

Renders binary output

b

Is defined by the IRender interface

c

BinaryDumpOutputStream out = new BinaryDumpOutputStream(buffer); out.setBytesPerLine(32); out.setShowAscii(true); InputStream in = _file.getStream(); copy(in, out); in.close(); out.close(); writer.print(buffer.getBuffer().toString()); }

catch (IOException ex) {

throw new ApplicationRuntimeException( "Unable to generate binary output.", ex); }

}

private void copy(InputStream in, OutputStream out) throws IOException

{

byte[] buffer = new byte[1000]; while (true)

{

int length = in.read(buffer); if (length < 0) return; out.write(buffer, 0, length); } } }

public IRender getContentRenderer() {

return new ContentRenderer(getFile()); }

}

An inner class is defined to render the binary content of the file as a hexadecimal dump. The IRender interface is the common interface for any kind of object,

component or not, that can be part of the page-rendering process. Gets content of uploaded file

d

Creating pop-up date selections using DatePicker 165 The render() method is the sole method defined by the IRender interface.

The uploaded file is an instance of IUploadFile. The getStream() method pro-

vides a binary input stream of the uploaded content in the file.

To get this output, we make use of a Delegator component. Delegator delegates its rendering to another object, an object that implements the IRender interface.

The rendering object is specified using the delegate parameter. The HTML tem- plate includes the Delegator component, which delegates to the content- Renderer property of the page:

<pre>

<span jwcid="@Delegator" delegate="ognl:contentRenderer"/> </pre>

All that’s left is to provide the contentRenderer property on the page:

public abstract IFile getFile(); public IRender getContentRenderer() {

return new ContentRenderer(getFile()); }

When the page renders, the Delegator component invokes the render() method

on the ContentRenderer instance created by this method; ContentRenderer is

responsible for reading the content of the uploaded file and outputting that con- tent, formatted by the BinaryDumpOutputStream instance, into the response.

The FileUpload component shows how a Tapestry component can perform considerable server-side processing. The next component, DatePicker, shows the kind of client-side logic that can be provided by a component.

In document Tapestry in Action (Page 191-196)