You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cocoon.apache.org by Ivelin Ivanov <iv...@iname.com> on 2002/04/05 04:36:45 UTC

Re: [Forms] Resubmit handling

Since the interest in Form handling has being going steadily up in the last
few weeks, thought we should bring the form discussion back to the list, so
more
people can participate.

One of the topics Torsten and I were discussing was a mechanism for handling
form resubmits.
This is the shield which protects people from paying twice for the same
item, by incidently double clicking on the Submit button. Or refreshing the
confirm page or pressing back, or bookmarking the confirm page, etc.
I guess everyone is familiar with this perpetual problem.

I've written some code which provides such protection mechanism a year ago,
which has been used in production since.
Struts has recently offered a similar mechanism in its Action class, which
also looks good.
http://cvs.apache.org/viewcvs.cgi/jakarta-struts/src/share/org/apache/struts
/action/Action.java
(look at the methods with Token in their name)

I'll submit my version below, so we can evaluate and pick one for the
AbstractPreceptorAction.

Here we go:

==========================


Populating response to include a hidden tag in the form:


    // Create an appropriate hidden input element
    // to carry the unique form token
    StringBuffer results = new StringBuffer();
    results.append("\n  <!-- RESUBMIT CONTROL TAG --> ");
    results.append("\n<input type=\"hidden\" name=\"_auxsubmitaction_\">");
    results.append("\n  <input type='hidden' ");
    results.append(" name='");
    results.append( ActionResubmitManager.REQUEST_PARAMETER_NAME );
    results.append("' ");
    results.append(" value='");
    results.append( ActionResubmitManager.getInstance().issueTicket(
pageContext.getSession() ) );
    results.append("' > \n");

    // Render the end of the table element to our writer
    writeOut( results.toString() );


---------------------

Actual resubmit manager:



import java.io.IOException;
import java.util.HashSet;

import javax.servlet.http.HttpSession;


/**
 * Issues and collects unique tickets for all ActionForms which
 * cannot handle resubmit themselves
 */
public class ActionResubmitManager
  {

  public static final String RESUBMIT_MANAGER_SESSION_KEY =
ActionResubmitManager.class + ".SESSION_KEY";
  public static final String REQUEST_PARAMETER_NAME =
ActionResubmitManager.class + ".REQUEST_PARAMETER_NAME";
  public static final String RESUBMIT_MAPPING_NAME = "resubmit";

  /**
   * Issues to the requesting FormTag an unique ticket
   * which will be used on submit to prevent accidental
   * resubmits (browser refresh or back buttons)
   */
  public String issueTicket(HttpSession session)
    {
    if (isFirstTicketRequest( session ))
      {
      initTicketFactory( session );
      }
    return getNextTicket( session );
    }

  /**
   * The GenericActionServlet returns tickets which
   * were used once by an action form.
   * The manager marks the ticket as used up
   * and subsequently issues new ticket to requesters.
   * If a ticket has been already used up and returned
   * then subsequent atempts for use the ticket
   * are treated as Resubmit actions and a ActionResubmitException
   * is thrown.
   *
   */
  public void useTicket(String ticket, HttpSession session) throws
ActionResubmitException
    {
    HashSet tickets = (HashSet) session.getAttribute(
RESUBMIT_MANAGER_SESSION_KEY );
    if (tickets == null) throw new ActionResubmitException ("Trying to use
invalid ticket. The user's http session probably expired.");
    synchronized (tickets)
      {
      boolean validTicket = tickets.remove(ticket);
      if (!validTicket) throw new ActionResubmitException("Trying to use an
already used ticket: " + ticket);
      }
    }


  protected boolean isFirstTicketRequest(HttpSession session)
    {
    return (session.getAttribute( RESUBMIT_MANAGER_SESSION_KEY ) == null);
    }


  protected synchronized void initTicketFactory(HttpSession session)
    {
    if (!isFirstTicketRequest( session )) return;
    HashSet tickets = new HashSet();
    session.setAttribute(RESUBMIT_MANAGER_SESSION_KEY, tickets);
    }


  protected String getNextTicket(HttpSession session)
    {
    HashSet tickets = (HashSet) session.getAttribute(
RESUBMIT_MANAGER_SESSION_KEY );
    String newTicket = new Guid().toString();
    synchronized (tickets)
      {
      tickets.add(newTicket);
      }
    return newTicket;
    }


  public static ActionResubmitManager getInstance()
    {
    if (instance_ == null)
      {
      createInstance();
      }
    return instance_;
    }

  protected synchronized static void createInstance()
    {
    if (instance_ == null)
      {
      instance_ = new ActionResubmitManager();
      }
    }

  private static ActionResubmitManager instance_ = null;
}


--------------------
Resubmit test case:


import org.apache.cactus.WebRequest;

public class ZActionResubmitManagerTest extends BaseServerTestCase
  {

  private static String testTicket_ = null;

  public ZActionResubmitManagerTest ( String name )
    {
    super( name );
    }


  public void setUp()
    {
    testTicket_ = ActionResubmitManager.getInstance().issueTicket( session
);
    System.out.println(ActionResubmitManager.class + " - setup: obtaining
ticket: " + testTicket_);
    }


  public void beginSingleSubmitTest( WebRequest theRequest )
    {
    // add a parameter similarly to the way a FormTag would do it
    String pname = ActionResubmitManager.REQUEST_PARAMETER_NAME;
    theRequest.addParameter( pname, testTicket_ );
    System.out.println(ActionResubmitManager.class + " - begin test:
submitting form. Request parameter " + pname + " = " + testTicket_ );
    }

  public void testResubmitTicketValidTimeSelection_Today() throws
ActionResubmitException
    {

    // use ticket
    String pname = ActionResubmitManager.REQUEST_PARAMETER_NAME;
    request.getAttribute( pname );

    System.out.println(ActionResubmitManager.class + " - in test: using
ticket first time " );
    ActionResubmitManager.getInstance().useTicket( testTicket_, session );
    System.out.println(ActionResubmitManager.class + " - in test: passed  "
);

    try
      {
      System.out.println(ActionResubmitManager.class + " - in test: using
ticket second time " );
      ActionResubmitManager.getInstance().useTicket( testTicket_, session );
      fail( " Attepmt to reuse the ticket should throw exception" );
      }
    catch ( ActionResubmitException ex)
      {
      System.out.println(ActionResubmitManager.class + " - in test: failed
as expected. " );
      // this is cool
      }
    }

  }







---------------------------------------------------------------------
To unsubscribe, e-mail: cocoon-dev-unsubscribe@xml.apache.org
For additional commands, email: cocoon-dev-help@xml.apache.org


Re: [Forms] Resubmit handling

Posted by Torsten Curdt <tc...@dff.st>.
> I'll submit my version below, so we can evaluate and pick one for the
> AbstractPreceptorAction.

cool, thanks
--
Torsten


---------------------------------------------------------------------
To unsubscribe, e-mail: cocoon-dev-unsubscribe@xml.apache.org
For additional commands, email: cocoon-dev-help@xml.apache.org