Quantcast
Channel: Developer Notes » Software Development
Viewing all articles
Browse latest Browse all 12

How to avoid primary page conversation timeout on background requests in Seam. Hacking around Seam request lifecycle

$
0
0

Sometimes it's better to avoid something. Avoid can be a solutionIt will be not easy to read and understand this article, so please remember that you always have a choice to not read it :-)
So,.. in our app we have few background processes on the page which are periodically ping server and update some page parts. Now I think this is not the greatest approach and we should use a real PUSH technology instead of periodic ping in every place where it is possible, but anyway. Our problem was that those requests (if they were handled by Seam Context-Filter) may cause our conversation (the page which user open in the browser) to be destroyed because of conversation timeout. And it really doesn’t work for us, since that really “secondary” requests may kill our primary conversation (especially if we have several browser windows opened)
So, to summarize the problem – “periodic seam requests (ajax requests) cause the destroying of the conversation”, so, if user don’t do any activity on the page during the conversation timeout – the next his action show the “Conversation timed out” message.

Partial solution here could be not use Seam ContextFilter for that requests, but in that case we couldn’t use our Seam components (so it don’t work for us)
The better solution (I think) – is to “Avoid timing-out the conversation in our background requests”. It is what will be described below. However the Much Better Solution would be also “Avoiding the background periodic requests by using a real push approach” – like it is shown in the IcePush integration post. But that not always work, but definitely preferable solution.
So, the workaround for us was to establish Seam environment for the request but don’t cause the the same environment as it is done in the org.jboss.seam.web.ContextFilter but don’t call the Manager.conversationTimeout()

Finally solution looks like 2 classes (created from Seam ContextFilter and ContextualHttpServletRequest)
pretty simple ContextFilterWithoutTimeout (it just create and run ContextualHttpServletRequestWOTimeout)

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.jboss.seam.web.AbstractFilter;

public class ContextFilterWithoutTimeout extends AbstractFilter {
   public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
       throws IOException, ServletException
   {
      new ContextualHttpServletRequestWOTimeout( (HttpServletRequest) request )
      {
         @Override
         public void process() throws ServletException, IOException
         {
            chain.doFilter(request, response);
         }
      }.run();
   }
}

and the ContextualHttpServletRequestWOTimeout which is a copy of ContextualHttpServletRequest with commented call to
// Manager.instance().endRequest( new ServletRequestSessionMap(request) );
so it doesn’t call the touchConversationStack() and Manager.instance().conversationTimeout(session);
as the result our request don’t update anything in the conversation stack and don’t cause the timing-out of conversations
“destroyConversation( conversationEntry.getId(), session );”
it doesn’t mean that conversations will not be timed out – they will, but they will do it as a result of primary user request (or request which is working through Seam ContextFilter). So we don’t change Seam request processing completely, but create the way to make exceptions in request processing, so we can create requests which will not cause our conversations to be timed-out.

   public void run() throws ServletException, IOException
   {
      log.debug("beginning request");

      // Force creation of the session
      if (request.getSession(false) == null)
      {
         request.getSession(true);
      }

      // Begin request and Seam life cycle only if it is not nested
      // ContextualHttpServletRequest
      if (getCounterValue() == 0)
      {
         ServletLifecycle.beginRequest(request);
         ServletContexts.instance().setRequest(request);
         restoreConversationId();
         Manager.instance().restoreConversation();
         ServletLifecycle.resumeConversation(request);
         handleConversationPropagation();
      }

      try
      {
         incrementCounterValue();

         process();

         decrementCounterValue();

         // End request only if it is not nested ContextualHttpServletRequest
         if (getCounterValue() == 0)
         {
//            Manager.instance().endRequest( new ServletRequestSessionMap(request)  );
            ServletLifecycle.endRequest(request);
         }
      }
      catch (IOException ioe)
      {
         removeCounter();
         Lifecycle.endRequest();
         log.debug("ended request due to exception");
         throw ioe;
      }
      catch (ServletException se)
      {
         removeCounter();
         Lifecycle.endRequest();
         log.debug("ended request due to exception");
         throw se;
      }
      catch (Exception e)
      {
         removeCounter();
         Lifecycle.endRequest();
         log.debug("ended request due to exception");
         throw new ServletException(e);
      }
      finally
      {
         log.debug("ended request");
      }
   }

in the web.xml we declare the filter and servlets which will use it. We map this filter to the servlets which make periodic background calls to the server

    <filter>
        <filter-name>No Conversation Timeout Filter</filter-name>
        <filter-class>ContextFilterWithoutTimeout</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>No Conversation Timeout Filter</filter-name>
        <servlet-name>LongOperationsServlet</servlet-name>
    </filter-mapping>
    <filter-mapping>
        <filter-name>No Conversation Timeout Filter</filter-name>
        <servlet-name>PollServlet</servlet-name>
    </filter-mapping>

As I said before – it doesn’t completely change the Seam request processing, it just let us to avoid conversation timeout for our primary page. In case user will open two pages and leave them for certain time (larger than configured conversation timeout) – only the first page he will interact with will continue conversation, because on the first interaction the other conversation 9form other page) will be timed out. But that is expected behavior of Seam conversations and we are not going to change it. What we solve here – is that our primary conversation will not be destroyed because of our background (secondary) calls and that those secondary calls will have an access to all Seam components.



Viewing all articles
Browse latest Browse all 12

Trending Articles