0 Replies Latest reply on Oct 7, 2009 11:41 AM by jdarst

    Transaction timeout using r:fileupload component

    jdarst

      In order to implement file upload functionality within a JSF+Seam portlet, we are utilizing the Richfaces file upload component <r:fileupload>. No portletbridge problems, but on very large file uploads (> 100MB-300MB depending on connection speeds), the transaction times out. We could just set the transaction timeout setting in jboss-service.xml to 0 or set it equal to our session timeout, but that certainly doesn't seem optimal. Since the transaction times out while the FileUploadPhaseListener (which gets called before the Restore View phase) is parsing the MultipartRequest, it won't do any good to make our UploadAction transactional since the transaction will have already timed out before the upload action listener gets called. I know that on a JSF request there are 2 global transactions + 1 optional: 1 that starts before Restore View phase (or before Apply Request Value depending on the tx mgmt used, but it's Restore View in our case since JBoss JTA is used) and ends after Invoke Application phase, one during the Render Response phase, and an optional one for any additional page actions. So it seems the best bet is to modify the time out on the transaction that commits at the end of the Invoke Application phase, let me know if this wrong though.

      First approach: Set the timeout in a phase listener

      - I can certainly access the UserTransaction within a phase listener. SeamPhaseListener does this as well. However, since any phase listeners defined in the faces-config in the WAR get registered after any phase listeners in config files pulled from libraries in WEB-INF/lib, including the richfaces FileUploadPhaseListener, I wasn't able to get my phase listener to get called before the MultipartRequest gets parsed.

      Second approach: Set the timeout in a filter

      - My filter does get called before the seam filter and before the a4j filter, which is the last filter in the chain before the JSF Lifecycle begins, and I'm able to retrieve a UserTransaction managed by JBossJTA and set the transaction timeout. The transaction certainly does not timeout while the MultipartRequest is being parsed. However during the Invoke Application phase, the application just hangs indefinitely. One thing I should note is that our UploadAction calls another component in our app which executes another http post request with the handle to our uploaded file (the temp file created by the r:fileupload component) to an external ECMS (Alfresco) which is run on a seperate JBoss instance. Also when I drop the http connection with Alfresco, the JSF Lifecycle continues. However I don't think the problem is with Alfresco because when I don't do anything to the transaction, it will timeout, but not until the end of the Invoke Application phase after the call to Alfresco.

      So my question is, am I changing the timeout on the correct transaction? Is it correct to modify this before or after JSF/Seam begins the Lifecycle, and if so what else needs to be done so that it doesn't interfere with the global transaction handling within the request?

      Is there another approach that would better suit this problem?

      Below is a copy of the web.xml and my filter that sets the timeout.

      web.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       version="2.5">
       <display-name>Collaborate</display-name>
       <context-param>
       <param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
       <param-value>
       org.jboss.portletbridge.application.FaceletPortletViewHandler
       </param-value>
       </context-param>
       <context-param>
       <param-name>org.jboss.portletbridge.ExceptionHandler</param-name>
       <param-value>
       org.jboss.portletbridge.SeamExceptionHandlerImpl
       </param-value>
       </context-param>
       <context-param>
       <param-name>javax.faces.LIFECYCLE_ID</param-name>
       <param-value>SEAM_PORTLET</param-value>
       </context-param>
       <context-param>
       <param-name>javax.portlet.faces.renderPolicy</param-name>
       <param-value>ALWAYS_DELEGATE</param-value>
       </context-param>
       <context-param>
       <param-name>org.ajax4jsf.RESOURCE_URI_PREFIX</param-name>
       <param-value>rfRes</param-value>
       </context-param>
       <context-param>
       <param-name>org.richfaces.LoadStyleStrategy</param-name>
       <param-value>DEFAULT</param-value>
       </context-param>
       <context-param>
       <param-name>org.richfaces.LoadScriptStrategy</param-name>
       <param-value>NONE</param-value>
       </context-param>
       <context-param>
       <param-name>org.ajax4jsf.COMPRESS_SCRIPT</param-name>
       <param-value>false</param-value>
       </context-param>
       <context-param>
       <param-name>javax.faces.CONFIG_FILES</param-name>
       <param-value>/WEB-INF/navigation.xml</param-value>
       </context-param>
       <context-param>
       <param-name>facelets.LIBRARIES</param-name>
       <param-value>
       /WEB-INF/fv.taglib.xml
       </param-value>
       </context-param>
      
       <listener>
       <listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
       </listener>
       <servlet>
       <servlet-name>Seam Resource Servlet</servlet-name>
       <servlet-class>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
       </servlet>
       <servlet-mapping>
       <servlet-name>Seam Resource Servlet</servlet-name>
       <url-pattern>/seam/resource/*</url-pattern>
       </servlet-mapping>
      
       <filter>
       <filter-name>File Upload Filter</filter-name>
       <filter-class>com.xyz.fv.presentation.collaborate.servlet.CollaborateFileUploadFilter</filter-class>
       </filter>
       <filter-mapping>
       <filter-name>File Upload Filter</filter-name>
       <servlet-name>Faces Servlet</servlet-name>
       <dispatcher>FORWARD</dispatcher>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>INCLUDE</dispatcher>
       </filter-mapping>
      
       <filter>
       <filter-name>Seam Filter</filter-name>
       <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
       </filter>
      
       <filter>
       <filter-name>Context Filter</filter-name>
       <filter-class>org.jboss.seam.web.ContextFilter</filter-class>
       </filter>
      
       <filter-mapping>
       <filter-name>Context Filter</filter-name>
       <url-pattern>/download/*</url-pattern>
       </filter-mapping>
      
       <!--
       <filter-mapping>
       <filter-name>Seam Filter</filter-name>
       <url-pattern>/download/*</url-pattern>
       </filter-mapping>
       -->
      
       <filter-mapping>
       <filter-name>Seam Filter</filter-name>
       <servlet-name>Faces Servlet</servlet-name>
       <dispatcher>FORWARD</dispatcher>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>INCLUDE</dispatcher>
       </filter-mapping>
      
       <filter>
       <display-name>Ajax4jsf Filter</display-name>
       <filter-name>ajax4jsf</filter-name>
       <filter-class>org.ajax4jsf.Filter</filter-class>
       <init-param>
       <param-name>createTempFiles</param-name>
       <param-value>true</param-value>
       </init-param>
       </filter>
       <filter-mapping>
       <filter-name>ajax4jsf</filter-name>
       <servlet-name>Faces Servlet</servlet-name>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>FORWARD</dispatcher>
       <dispatcher>INCLUDE</dispatcher>
       </filter-mapping>
      
       <!--
       <context-param>
       <param-name>facelets.BUILD_BEFORE_RESTORE</param-name>
       <param-value>true</param-value>
       </context-param>
       -->
       <context-param>
       <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
       <param-value>.xhtml</param-value>
       </context-param>
       <context-param>
       <param-name>facelets.DEVELOPMENT</param-name>
       <param-value>false</param-value>
       </context-param>
      
       <servlet>
       <servlet-name>Faces Servlet</servlet-name>
       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
       <load-on-startup>1</load-on-startup>
       </servlet>
       <servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>/faces/*</url-pattern>
       </servlet-mapping>
       <servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>*.seam</url-pattern>
       </servlet-mapping>
       <session-config>
       <session-timeout>30</session-timeout>
       </session-config>
       <servlet>
       <servlet-name>DownloadServlet</servlet-name>
       <servlet-class>
       com.xyz.fv.presentation.collaborate.DownloadServlet</servlet-class>
       <load-on-startup>1</load-on-startup>
       </servlet>
       <servlet-mapping>
       <servlet-name>DownloadServlet</servlet-name>
       <url-pattern>/download/*</url-pattern>
       </servlet-mapping>
      
      
      
       <error-page>
       <exception-type>javax.servlet.ServletException</exception-type>
       <location>/servletError.xhtml</location>
       </error-page>
      
      
      
      </web-app>
      



      CollaborateFileUploadFilter.java
      package com.xyz.fv.presentation.collaborate.servlet;
      
      
      
      import java.io.IOException;
      
      
      
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      
      
      
      import com.xyz.fv.common.web.app.servlet.FileUploadFilter;
      
      
      
      public class CollaborateFileUploadFilter extends FileUploadFilter
      {
       @Override
       public void init(FilterConfig config)
       {
       super.init(config);
       }
      
      
      
       @Override
       public void destroy()
       {
       // TODO Auto-generated method stub
       }
      
      
      
       @Override
       public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
       {
       super.doFilter(request, response, chain);
       }
      }
      



      FileUploadFilter.java (within ejb-module packaged with the war but not in the same war)
      
      package com.xyz.fv.common.web.app.servlet;
      
      
      
      import java.io.IOException;
      
      
      
      import javax.naming.InitialContext;
      import javax.naming.NameNotFoundException;
      import javax.naming.NamingException;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServletRequestWrapper;
      import javax.transaction.SystemException;
      import javax.transaction.UserTransaction;
      
      
      
      import org.ajax4jsf.request.MultipartRequest;
      import org.apache.log4j.Logger;
      
      
      
      public class FileUploadFilter extends BaseFilter
      {
       private final Logger log = Logger.getLogger(FileUploadFilter.class);
      
       private InitialContext initContext;
      
      
      
       @Override
       public void init(FilterConfig config)
       {
       super.init(config);
      
       }
      
       @Override
       public void destroy()
       {
       // TODO Auto-generated method stub
       }
      
      
      
       @Override
       public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
       {
       try
       {
       initContext = new InitialContext();
       }
       catch (NamingException ne)
       {
       throw new RuntimeException(ne);
       }
      
       HttpServletRequestWrapper requestWrapper = null;
      
      
      
       if (request instanceof HttpServletRequestWrapper)
       {
       requestWrapper = (HttpServletRequestWrapper) request;
       }
      
      
      
       if ((requestWrapper != null && requestWrapper.getRequest() instanceof MultipartRequest) || request instanceof MultipartRequest)
       {
       try
       {
       UserTransaction ut = null;
       try
       {
       ut = (UserTransaction) initContext.lookup("java:comp/UserTransaction");
       }
       catch (NameNotFoundException nnfe)
       {
       try
       {
       // Embedded JBoss has no java:comp/UserTransaction
       ut = (UserTransaction) initContext.lookup("UserTransaction");
       // ut.getStatus(); //for glassfish, which can return an unusable UT
       }
       catch (Exception e)
       {
       throw nnfe;
       }
       }
      
      
      
       int txTimeout = Integer.parseInt(System.getProperty("long.tx.timeout"));
       ut.setTransactionTimeout(txTimeout);
       }
       catch (NamingException ne)
       {
       throw new RuntimeException(ne);
       }
       catch (NumberFormatException nfe)
       {
       log.warn("Unable to modify tx timeout. Transaction timeout value must be an integer.");
       }
       catch (SystemException se)
       {
       log.warn("Unable to modify tx timeout due to system exception.", se);
       }
       }
      
      
      
       chain.doFilter(request, response);
       }
      }
      



      BaseFilter.java

      
      package com.xyz.fv.common.web.app.servlet;
      
      
      
      import javax.servlet.Filter;
      import javax.servlet.FilterConfig;
      
      
      
      public abstract class BaseFilter implements Filter
      {
       @Override
       public void init(FilterConfig config)
       {
       }
      }