Adding attachment support to the worklist

I have just posted a new version of the worklist sample which adds the ability to add an attachment to a task.  Let’s take a look how we add and view (download) task attachments.

First, let’s review the addAttachment method in the MTaskList class.  Here is the code (as always, there are more comments in Subversion):

  public static void addAttachment(String user, String taskNumber, MultipartFile attachment) {
    MLog.log("MTaskList", "Entering addAttachment()");

    try {

      // login
      ctx = ContextCache.getContextCache().get(user);

      // get task details
      Task task = getTaskQueryService().getTaskDetailsByNumber(ctx, Integer.parseInt(taskNumber));

      // add the attachment
      getTaskService();
      MLog.log("MTaskList", "Adding attachment to task " + task.getSystemAttributes().getTaskNumber());

      AttachmentType xAttachment = new ObjectFactory().createAttachment();
      xAttachment.setName(attachment.getOriginalFilename());
      xAttachment.setInputStream(attachment.getInputStream());
      xAttachment.setMimeType(attachment.getContentType());
      xAttachment.setDescription(attachment.getOriginalFilename());

      getTaskService().addAttachment(ctx, task.getSystemAttributes().getTaskId(), xAttachment);

      MLog.log("MTaskList", "Leaving addAttachment()");

    } catch (Exception e) {
      e.printStackTrace();
    }

  }

In this code, you can see that we need to get an instance of AttachmentType from the ObjectFactory by calling its createAttachment() method.  We can then fill in the appropriate details – name, MIME type, description, and set the actual content of the attachment.  We need to have our data in an InputStream inside a MultipartFile.  The Spring framework and our controller will handle this for us, let’s take a look at it now.

Here is the code for the AddAttachmentController:

  public AddAttachmentController() {
    setCommandClass(FileUpload.class);
    setCommandName("addattachment.do");
  }

  @Override
  protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
      Object command, BindException errors) throws Exception {
    MLog.log("AddAttachmentController", "Entering onSubmit()");
    // cast the uploaded object to our domain class
    FileUpload file = (FileUpload) command;

    // get the file out of the domain object
    MultipartFile multipartFile = file.getFile();

    // check if there is any data in the file
    if (multipartFile != null) {
      MLog.log("AddAttachmentController", "We seem to have a file");
    } else {
      MLog.log("AddAttachmentController", "We do not seem to have a file");
    }

    // handle the attachment here
    String taskNumber = request.getParameter("x_tasknumber");
    MTaskList.addAttachment(request.getRemoteUser(), taskNumber, multipartFile);

    // send the user back to the task detail page (where they were)
    return (new TaskDetailController()).handleRequest(request, response);
  }

We are using the Spring Multipart File Upload capability to get our file from the user into our application.  To do this, we need to define special class which will hold our uploaded file.  This is the FileUpload class shown below.  Notice how the controller calls two special methods to identify this class and the command name (“addattachment.do”).

Next, since we are using a Spring Form here, instead of a POST, we need to implement and override the onSubmit() method.  This is different to what we have seen in our other controllers.  Note that the binary data is passed in to this method by the Spring Form framework as Object command. The first thing we need to do is to cast this to our FileUpload class.  We can then call the getFile() method on this to get the actual MultipartFile.  This is what we pass to our addAttachment method which we saw earlier.  It contains the binary data and also metadata about the file.

Finally, we use the Controller-chaining method to pass the user back to the Task Detail page (where they just came from) which will reload and will now show the attachment we just uploaded.  This is done by returning the output of the handleRequest method on a new instance of the controller for that page, to which we have passed our request and response objects.

public class FileUpload {

  private MultipartFile file;

  public MultipartFile getFile() { return file; }
  public void setFile(MultipartFile file) { this.file = file; }

}

So that covers uploading attachments, now let’s take a look at how we download attachments.  Here is the code for our DownloadAttachmentController:

public class DownloadAttachmentController extends SimpleSuccessFailureController {

  private static final int IO_BUFFER_SIZE = 1;

  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    MLog.log("DownloadAttachmentController", "Entering handleRequest()");
    // get parameters from request
    String xTasknumber = request.getParameter("x_tasknumber");
    String xFile = request.getParameter("x_file");
    String xVersion = request.getParameter("x_version");

    MLog.log("DownloadAttachmentController", "Attachment details: tasknumber=" + xTasknumber +
             " file=" + xFile + " version=" + xVersion);

    // TODO move this BPM API specific stuff out of the controller into the domain
    IWorkflowContext ctx = ContextCache.getContextCache().get(request.getRemoteUser());

    // get the task id
    String taskid = MTaskList.getTaskIdFromNumber(xTasknumber, request.getRemoteUser());

    // get the inputStream from the attachment
    InputStream in = WorkflowAttachmentUtil.getAttachment(
        ctx,                                                         // context
        MTaskList.getServerURL(),                                    // SOA URL
        taskid,                                                      // task id
        Integer.parseInt(xVersion),                                  // attachment version
        xFile,                                                       // file name
        null);                                                       // logger

    // set up the response
    response.setContentType("application/octet-stream");             // file.getContentType()
    response.setHeader("Content-Disposition", "attachment; filename=" + xFile);

    ServletOutputStream out = response.getOutputStream();
    copy(in, out);
    out.flush();
    out.close();
    return null;
  }

  private static void copy(InputStream in, OutputStream out) throws IOException {
    byte[] b = new byte[IO_BUFFER_SIZE];
    int read;
    while ((read = in.read(b)) != -1) {
      out.write(b, 0, read);
    }
  }

}

I have been a bit naughty here and broken my own separation rule.  I have some BPM API specific code in the controller here.  I will move that into the domain package where it belongs, but for now, let’s take a look at how it works.

It is similar to many of the previous examples.  We extract the parameters from the request object, then we get the context and lookup the taskId, we have seen all of this before, so I will not cover it again.  Next, we need to call the WorkflowAttachmentUtil.getAttachment() method.  This WorkflowAttachmentUtil is a helper class that we can use to get the attachment.  This gives us the binary data from the attachment in an InputStream.  This API may change in the future.

Now we use another little Spring trick to send this to the browser.  Here is that part of the code again.  Basically, we are setting the content type in the response manually to application/octet-stream which means any kind of binary data.  Then we set a Content-Disposition header that tells the browser it needs to download this attachment and what the filename is.  Most browsers will work out how to do the right thing based on this information.  Next we just copy the binary data from the InputStream straight into the ServletOutputStream and return null.

    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=" + xFile);

    ServletOutputStream out = response.getOutputStream();
    copy(in, out);
    out.flush();
    out.close();
    return null;

Finally, let’s take a look at the taskdetail.jsp view which invokes all of this functionality.  Here is the appropriate section of that view:

        <h2 class="td-h2">Attachments</h2>
        <table width="50%" class="tl-table">
          <tr>
            <th class="tl-head" width="150">User</th>
            <th class="tl-head" width="200">Date</th>
            <th class="tl-head">Name</th>
          </tr>
        </table>
        <table width="50%">
          <c:forEach items="${model.task.attachments}" var="attachment">
            <tr>
              <td class="tl-row" width="150">${attachment.updatedBy}</td>
              <td class="tl-row" width="200">${attachment.updatedDate.time}</td>
              <td class="tl-row">
                <a href="downloadattachment.do?x_tasknumber=${model.task.number}&x_file=${attachment.name}&x_version=${attachment.version}"
                   target="_blank">${attachment.name}</a>
              </td>
            </tr>
          </c:forEach>
          <tr><td>Add a new attachment:</td></tr>
          <tr>
            <td colspan="3"><form action="addattachment.do" method="POST" enctype="multipart/form-data">
              <input type="file" name="file"/>
              <input type="hidden" name="x_tasknumber" value="${model.task.number}"/>
              <input type="submit" value="Add Attachment"/>
            </form></td>
          </tr>
        </table>

The first part, which handles the download is pretty straight forward and very similar to previous examples we have seen, so I wont go into detail.  Note though that the download link points to downloadattachment.do and has a target=”_blank” so that the browser will perform the download in a new tab/window and leave the one running our application alone.

The second part is the upload.  The interesting part here is the form tag.  Note that we specify enctype=”multipart/form-data” and also, and most importantly, that the input of type=”file” is named file, which matches the name of the property in the FileUpload class.  This is the one that will create the file upload functionality for us.

<form action="addattachment.do" method="POST" enctype="multipart/form-data">
<input type="file" name="file"/>

Well, that’s it for attachments, for now at least.  Enjoy!

About Mark Nelson

Mark Nelson is an Architect (an "IC6") in the Fusion Middleware Central Development Team at Oracle. Mark's job is to make Fusion Middleware easy to use in the cloud and at home, for developers and operations folks, with special focus on continuous delivery, configuration management and provisioning - making it simple to manage the configuration of complex environments and applications built with Oracle Database, Fusion Middleware and Fusion Applications, on-premise and in the cloud. Before joining this team, Mark was a senior member of the A-Team since 2010, and worked in Sales Consulting at Oracle since 2006 and various roles at IBM since 1994.
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s