Accepting File Uploads in your WebApp in 10 Minutes or Less

Preamble

In a project I’m currently working on, I have a requirement for handling user-uploaded images.  The project uses Spring Roo for rapid development of the webapp, and the choice for cloud plaform service provider has been narrowed to either VMWare’s CloudFoundry, Amazon EC2, or Google App Engine.  The example in this post should run on CloudFoundry and EC2, but to run on AppEngine, there will be some tweaks required to prevent attempts to access the filesystem.  I may revisit this post later to discuss changes required for running on Google AppEngine in more detail.

Prequisites

In this post I will assume the reader is somewhat familiar with Spring Roo, so I won’t be explaining much about Roo itself.    To get up to speed, I suggest visiting the Spring Roo website.  If you’re already a Spring Roo user, please ensure you’re using the latest version, 1.2.1 at the time of this writing, because some of the features I discuss here where very recently added to Spring Roo.

Demo: Adding File Upload support using Spring Roo and Spring MVC

  1. Create a new Spring Roo project.
    1. If you’re using the Spring Tool Suite (STS), use File, New, Spring Roo Project.  Give your project a name, and a top level package name, then click finish.  After the project is created, you will see the Spring Roo console open, from this point you will follow the console instructions, not including the line below.
    2. If you’re using the console, type
      project --topLevelPackage ca.coolman.demo --projectName MediaUploadDemo --java 6 --packaging JAR

    I will use the package ca.coolman.demo for this post.

  2. We’ll start by setting up persistence to store metadata about the uploaded image, which we’ll do in the Roo Shell. For this demo, I will use a Hypersonic embedded database for ease of setup.  In the Roo Shell, type the following:
    jpa setup --database H2_IN_MEMORY --provider HIBERNATE
  3. Now, to create our model, we’ll run the following command in the Roo Shell.
    entity jpa --class ~.model.MediaUpload
    field string --fieldName filepath --notNull true --sizeMax 128
    field number --type long --fieldName filesize --notNull true
    field string --fieldName contentType --notNull true
  4. We need to modify the generated model code to add a transient content field. We’re making the field transient in the model only, the data is persisted to the filesystem within the controller, which passes the file path to the persistent filename field in the model.   With that said, edit the file src/main/java/ca/coolman/demo/model/MediaUpload.java, and add the following:
    @Transient
    private byte[] content;
  5. Now we’re ready to scaffold the CRUD views and controllers, by executing the following commands in the Roo Shell:
    web mvc setup
    web mvc all --package ~.web
  6. We’ll override the CREATE method of the generated controller, so that we can persist the uploaded file contents to the filesystem.  I simply pulled the code below from the generated controller ITD, and added the required code for capturing and moving the uploaded file.  (I also removed the generated test for bindingResult.hasErrors() because it will complain about our empty filepath, size, and contentType fields, which are marked in the model as notNull). When we re-save the controller, Spring Roo will automatically update the controller ITD, removing the generated CREATE method.
    @RequestMapping(method = RequestMethod.POST, produces = "text/html")
    public String create(@Valid MediaUpload mediaUpload, BindingResult bindingResult, Model uiModel,
           @RequestParam("content") CommonsMultipartFile content,
           HttpServletRequest httpServletRequest) {
       File dest = new File("/tmp/" + content.getOriginalFilename());
       try {
          content.transferTo(dest);
          mediaUpload.setFilesize(content.getSize());
          mediaUpload.setFilepath(dest.getAbsolutePath());
          mediaUpload.setContentType(content.getContentType());
       } catch (Exception e) {
          e.printStackTrace();
          return "mediauploads/create";
       }
    
       uiModel.asMap().clear();
       mediaUpload.persist();
       return "redirect:/mediauploads/" + encodeUrlPathSegment(mediaUpload.getId().toString(),
          httpServletRequest);
    }

    In this demo, I’m sending all the files to the /tmp/ dir, you would normally define a location for uploaded files to be stored here.

  7. Now, the CREATE view created by the scaffolding step will render editors for each of the fields of our model bean by default.  We don’t want to present these editors to the user, so, edit the file: src/main/webapp/WEB-INF/views/mediauploads/create.jspx and for each field under for form, add an attribute to the node: render=”false”.
  8. To add an input for selecting the file from the local filesystem to upload, we have a few steps:
    1. First we need to add the following field within the form:
       <field:input field="content" id="c_ca_coolman_demo_model_MediaUpload_content" required="true" type="file" z="user-managed"/>
    2. Edit the file /MediaUploadDemo/src/main/webapp/WEB-INF/i18n/application.properties, add the line:
      label_ca_coolman_demo_model_mediaupload_content=Content
    3. Now, this part is a bit of a hack (I’m sure Spring Roo supports field of type=”file” out of the box, but I couldn’t find it), edit the tag file: src/main/webapp/WEB-INF/tags/form/fields/input.tagx, and look for the following:
      <c:when test="${type eq 'password'}">

      Right above this line, add the following code:

      <c:when test="${type eq 'file'}">
         <form:input type="file" id="_${sec_field}_id" path="${sec_field}" disabled="${disabled}" />
      </c:when>
  9. Still in the CREATE view script, we need to set our form encoding to multipart/form-encoded.  We do this by adding the following attribute to the form node:  multipart=”true”
  10. Finally, we’ll enable multipart filtering using tools included with Spring Framework. This will automagically take care of the binding work that we’d normally be required to do manually. In your IDE, open the file src/main/resources/META-INF/applicationContext.xml. Add the following after the last <bean> node in the file:
    <bean id="multipartFilter" class="org.springframework.web.multipart.support.MultipartFilter">
       <property name="multipartResolverBeanName" value="multipartResolver" />
    </bean>
    <bean id="multipartResolver"
       class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
  11. The multipart filtering added above has a dependency on Apache Commons IO, so open the pom.xml file and add the following under the dependencies node (or use the Maven POM editor, dependencies tab):
    <dependency>
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
       <version>2.1</version>
    </dependency>
  12. Now open the file src/main/webapp/WEB-INF/web.xml, and add the following in the <filters>section of the file:
    <filter>
       <filter-name>multipartFilter</filter-name>
       <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
       <filter-name>multipartFilter</filter-name>
       <url-pattern>/*</url-pattern>
    </filter-mapping>

Try it out

Now, to fire up the app, drop to the terminal, go to the project root directory, and type: mvn jetty:run, then point your web browser to http://localhost:8080, click the context provided link, and you should see something that resembles the following:

File Upload View in Spring Roo

Clicking on the content field will present the browse button to select a file.  When you click save, the file will be sent to your tmp/ directory, and you will be presented with the view of the file attributes.  I’ll leave the retrieval and display of the uploaded file contents as an exercise for the reader.

Spring Roo File Upload Show Script

Like this post?

If you like this blog posting, please don’t forget to click the Like buttons.

Further Reading

As of this writing, a new Spring Roo book from Manning has just been released to the presses (April 2012), check it out:

The following books are also on the shelves, and are relatively up to date as well: