X

Best Practices from Oracle Development's A‑Team

Adding attachment support to the worklist

Mark Nelson
Architect

Introduction

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.

Main Article

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

# Copyright 2012 Oracle Corporation. # All Rights Reserved. # # Provided on an 'as is' basis, without warranties or conditions of any kind, # either express or implied, including, without limitation, any warranties or # conditions of title, non-infringement, merchantability, or fitness for a # particular purpose. You are solely responsible for determining the # appropriateness of using and assume any risks. You may not redistribute. 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!

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha