28/07/2013

[Java 4] Download file from portlet

Scenario: Java 4, Spring 2, WebSphere 6, JSR-168. How the hell do I let the user download a file from a portlet?

The file is extracted from a database and does not exist on the server's filesystem yet; redirecting to a servlet to create and send the response which contains the file to download fails with a generic:

java.lang.IllegalStateException: Can't invoke sendRedirect() after certain methods have been called

More specifically, that method can not be invoked after any of the following methods of the ActionResponse interface has been called:
  •         setPortletMode
  •         setWindowState
  •         setRenderParameter
  •         setRenderParameters
  •         removePublicRenderParamter
And currently I don't know whether it's Spring or WebSphere calling one of those forbidden methods silently.

However there's a working yet nasty workaround. It's bad and you should feel bad, but it works.. ish

Temporarily create the file placing it inside the portal directory where the portlet WAR has been unpacked, then link directly to it, remembering to delete it after some time. This can all be achieved from directly inside a JSP:

 [...]  
   
 <%  
 /*before landing on the JSP the attachments where recovered as Blobs from the DB and stored in a session attribute called "attachments"  
 we now recover it*/  
 List attachments = (List)results.getAttribute("attachments");  
 if(attachments!=null){//important to avoid NullPointerExceptions!!  
      Iterator iter = attachments.iterator();  
      //for each attachment present a link to download it  
      while(iter.hasNext()){  
           Blob b = (Blob)iter.next();  
           //check if it's not just and empty blob  
           if(b!=null){  
               
                InputStream is = b.getBinaryStream();  
               
                File myWar = null;  
               
                try{  
                /*each deployed portlet will be assigned an unique name which does not change on subsequent deploys. Once you find the full path  
                to the portlet's folder you should put it here.  
                The path should be something like:  
                /opt/WebSphere/PortalServer/installedApps/PORTLET_NAME_RANDOM_STRING.ear/SAME_RANDOM_STRING.war  
                and you may want to add a /resources at the end to keep things a little tidier  
                */  
                 myWar = new File("YOUR_PATH");  
                 if(!myWar.exists()){  
                      myWar.mkdir();  
                 }  
                }  
                catch(Exception e){  
                     //do something  
                }  
          
                File f = null;  
                try{  
                     //create temp file to be downloaded  
                     f= File.createTempFile("SOME_NAME", "SOME_EXTENSION", myWar);  
                }catch(Exception e){  
                     //do something  
                }  
                //write the blob to the temp file  
                FileOutputStream fos = new FileOutputStream(f.getAbsolutePath());  
                int read = 0;  
                byte[] bytes = new byte[1024];  
          
                while ((read = is.read(bytes)) != -1) {  
                     fos.write(bytes, 0, read);  
                }  
               
                fos.close();  
               
                String folder = "/resources/"+f.getName();  
                  
                //finally output the link to download the file  
           %>  
   
                <a target="_blank" href="<c:url value="<%=folder%>" />">"Download your file"</a>  
           <%  
           }  
      }  
 }  
 %>  
   
 [...]  


Of course, suggestions for more practical and clean solutions are well accepted.


No comments:

Post a Comment

With great power comes great responsibility