You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Paul Ivancsics <pa...@anecon.com> on 2004/05/26 10:30:36 UTC

SynchronizerToken pattern

We recently brought a Tapestry app into operation, about 80 pages
and components.

My colleague Bernhard Woditschka implemented the SynchronizerToken
there from "Core J2EE Patterns" by Alur, Crupi and Malks
(http://www.refactoring.com/catalog/introduceSynchronizerToken.html),
addressing the following issues:
- prevent use of browser "Back" button
- prevent multiple concurrent submissions of a form
- prevent operation in multiple browser windows using the same HTTP session

The implementation is 100% server side, and it is somewhat strict in
that upon detecting disallowed action on any page, an error page is
activated in any case.

Design summary:
---------------
The Visit object holds a FlowSynchronizer object responsible for
generating and comparing a unique token.

On every protected page, a hidden field is included in the form:
    <input jwcid="@Hidden" value="ognl:visit.flowSynchronizer.token"/>
This field holds a token which is generated by the FlowSynchronizer
when the page is rendered.

Upon form submission, the token parameter passed with the form is
compared to the token recently generated. A discrepancy triggers a
FlowSequenceException resulting in the Exception page rendered.
A match consumes the token so a subsequent duplicate submission fails.

The Exception page renders, depending on the exception thrown,
either the general error text, or an error text specific to the
FlowSequenceException.


Feel free to utilise or comment on this posting.
Paul Ivancsics
--------------
Anecon Software Design und Beratung G.m.b.H.
Vienna, Austria


FlowSynchronizer.java:
----------------------
import java.io.Serializable;
/**
   * Implementation of Synchronizer Token J2EE Patterns for Tapestry.
   *
   * Inclusion in Tapestry page template:
   * <code>
   * &lt;input jwcid="@Hidden"
value="ognl:visit.flowSynchronizer.token"/&gt;
   * </code>
   *
   * In case of a 2nd form in the same page:
   * <code>
   * &lt;input jwcid="@Hidden"
value="ognl:visit.flowSynchronizer.tokenCopy"/&gt;
   * </code>
   *
   * @author Bernhard Woditschka
   */
public class FlowSynchronizer implements Serializable {
      private long sequence;
      private String token;
      public FlowSynchronizer() {
          // initialize the sequence randomly
          sequence = (long) (Math.random() * Long.MAX_VALUE / 2l);
      }
      public String getToken() {
          // generate a new token
          token = Long.toHexString(++sequence);
          return token;
      }
      public String getTokenCopy() {
          return token;
      }
      public void setToken(String token) throws FlowSequenceException {
          // first compare the token
          if (this.token == null || ! this.token.equals(token))
          	throw new FlowSequenceException();
          // reset token on match -> subsequent duplicate submission
will fail
          this.token = null;
      }
      public void setTokenCopy(String tokenCopy) throws
FlowSequenceException {
          setToken(token);
      }
}

FlowSequenceException.java:
---------------------------
/**
   * Signals a page flow exception.
   *
   * @author Bernhard Woditschka
   */
public class FlowSequenceException extends Exception {
      public FlowSequenceException() {
      }
}

Exception.html:
---------------
<span jwcid="@Border">
            <span jwcid="@Conditional" condition="ognl:!flowSequenceError">
              <!--Block Start: Unhandled Error ++-->
			General error text bla bla
              <!--Block End: Unhandled Error ++-->
            </span>
            <span jwcid="@Conditional" condition="ognl:flowSequenceError">
              <!--Block Start: Flow sequence Error ++-->
              You have been using your browser's "Back" button, or jadda
jadda
              <!--Block End: Flow sequence Error ++-->
            </span>
</span>

ExceptionPage.java:
-------------------
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageRenderListener;
import org.apache.tapestry.html.BasePage;
import org.apache.tapestry.util.exception.ExceptionAnalyzer;
import org.apache.tapestry.util.exception.ExceptionDescription;
/**
   * The Tapestry Exception page.
   *
   * @author Bernhard Woditschka
   */
public class ExceptionPage extends BasePage implements PageRenderListener {
      // Causing exception
      private Throwable exception;
      // Flag signaling a FlowSequenceException
      private boolean flowSequenceError = false;
      public void pageBeginRender(PageEvent event) {
          IRequestCycle cycle = getRequestCycle();
          if (! cycle.isRewinding() && getException() != null) {
              // find the root cause
              ExceptionDescription[] ed = new
ExceptionAnalyzer().analyze(getException());
              String rootCauseExceptionName = ed[ed.length -
1].getExceptionClassName();
              // check for FlowSequenceException
              flowSequenceError =
FlowSequenceException.class.getName().equals(rootCauseExceptionName);
          }
      }
      public boolean isFlowSequenceError() {
          return flowSequenceError;
      }
      public Throwable getException() {
          return exception;
      }
      public void setException(Throwable exception) {
          this.exception = exception;
      }
}


---------------------------------------------------------------------
To unsubscribe, e-mail: tapestry-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tapestry-user-help@jakarta.apache.org


Re: SynchronizerToken pattern

Posted by Bryan Lewis <br...@maine.rr.com>.
That rocks!  The idea, the implementation, the code sharing.



---------------------------------------------------------------------
To unsubscribe, e-mail: tapestry-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tapestry-user-help@jakarta.apache.org


RE: SynchronizerToken pattern

Posted by "Howard M. Lewis Ship" <hl...@comcast.net>.
Similar to my thoughts on this.

I had been thinking of this in terms of a persistent page property (inside the component) and a
reusable component to nest inside the Form component.  Ideally, the component would have a listener
that is triggered on an error, to allow a more reasonable recovery.

The component would implement PageBeginRender and use that to allocate a new token.

--
Howard M. Lewis Ship
Independent J2EE / Open-Source Java Consultant
Creator, Jakarta Tapestry
Creator, Jakarta HiveMind
http://howardlewisship.com


> -----Original Message-----
> From: Paul Ivancsics [mailto:paul.ivancsics@anecon.com] 
> Sent: Wednesday, May 26, 2004 4:31 AM
> To: tapestry-user@jakarta.apache.org
> Subject: SynchronizerToken pattern
> 
> 
> We recently brought a Tapestry app into operation, about 80 pages
> and components.
> 
> My colleague Bernhard Woditschka implemented the SynchronizerToken
> there from "Core J2EE Patterns" by Alur, Crupi and Malks
> (http://www.refactoring.com/catalog/introduceSynchronizerToken.html),
> addressing the following issues:
> - prevent use of browser "Back" button
> - prevent multiple concurrent submissions of a form
> - prevent operation in multiple browser windows using the 
> same HTTP session
> 
> The implementation is 100% server side, and it is somewhat strict in
> that upon detecting disallowed action on any page, an error page is
> activated in any case.
> 
> Design summary:
> ---------------
> The Visit object holds a FlowSynchronizer object responsible for
> generating and comparing a unique token.
> 
> On every protected page, a hidden field is included in the form:
>     <input jwcid="@Hidden" value="ognl:visit.flowSynchronizer.token"/>
> This field holds a token which is generated by the FlowSynchronizer
> when the page is rendered.
> 
> Upon form submission, the token parameter passed with the form is
> compared to the token recently generated. A discrepancy triggers a
> FlowSequenceException resulting in the Exception page rendered.
> A match consumes the token so a subsequent duplicate submission fails.
> 
> The Exception page renders, depending on the exception thrown,
> either the general error text, or an error text specific to the
> FlowSequenceException.
> 
> 
> Feel free to utilise or comment on this posting.
> Paul Ivancsics
> --------------
> Anecon Software Design und Beratung G.m.b.H.
> Vienna, Austria
> 
> 
> FlowSynchronizer.java:
> ----------------------
> import java.io.Serializable;
> /**
>    * Implementation of Synchronizer Token J2EE Patterns for Tapestry.
>    *
>    * Inclusion in Tapestry page template:
>    * <code>
>    * &lt;input jwcid="@Hidden"
> value="ognl:visit.flowSynchronizer.token"/&gt;
>    * </code>
>    *
>    * In case of a 2nd form in the same page:
>    * <code>
>    * &lt;input jwcid="@Hidden"
> value="ognl:visit.flowSynchronizer.tokenCopy"/&gt;
>    * </code>
>    *
>    * @author Bernhard Woditschka
>    */
> public class FlowSynchronizer implements Serializable {
>       private long sequence;
>       private String token;
>       public FlowSynchronizer() {
>           // initialize the sequence randomly
>           sequence = (long) (Math.random() * Long.MAX_VALUE / 2l);
>       }
>       public String getToken() {
>           // generate a new token
>           token = Long.toHexString(++sequence);
>           return token;
>       }
>       public String getTokenCopy() {
>           return token;
>       }
>       public void setToken(String token) throws 
> FlowSequenceException {
>           // first compare the token
>           if (this.token == null || ! this.token.equals(token))
>           	throw new FlowSequenceException();
>           // reset token on match -> subsequent duplicate submission
> will fail
>           this.token = null;
>       }
>       public void setTokenCopy(String tokenCopy) throws
> FlowSequenceException {
>           setToken(token);
>       }
> }
> 
> FlowSequenceException.java:
> ---------------------------
> /**
>    * Signals a page flow exception.
>    *
>    * @author Bernhard Woditschka
>    */
> public class FlowSequenceException extends Exception {
>       public FlowSequenceException() {
>       }
> }
> 
> Exception.html:
> ---------------
> <span jwcid="@Border">
>             <span jwcid="@Conditional" 
> condition="ognl:!flowSequenceError">
>               <!--Block Start: Unhandled Error ++-->
> 			General error text bla bla
>               <!--Block End: Unhandled Error ++-->
>             </span>
>             <span jwcid="@Conditional" 
> condition="ognl:flowSequenceError">
>               <!--Block Start: Flow sequence Error ++-->
>               You have been using your browser's "Back" 
> button, or jadda
> jadda
>               <!--Block End: Flow sequence Error ++-->
>             </span>
> </span>
> 
> ExceptionPage.java:
> -------------------
> import org.apache.tapestry.IRequestCycle;
> import org.apache.tapestry.event.PageEvent;
> import org.apache.tapestry.event.PageRenderListener;
> import org.apache.tapestry.html.BasePage;
> import org.apache.tapestry.util.exception.ExceptionAnalyzer;
> import org.apache.tapestry.util.exception.ExceptionDescription;
> /**
>    * The Tapestry Exception page.
>    *
>    * @author Bernhard Woditschka
>    */
> public class ExceptionPage extends BasePage implements 
> PageRenderListener {
>       // Causing exception
>       private Throwable exception;
>       // Flag signaling a FlowSequenceException
>       private boolean flowSequenceError = false;
>       public void pageBeginRender(PageEvent event) {
>           IRequestCycle cycle = getRequestCycle();
>           if (! cycle.isRewinding() && getException() != null) {
>               // find the root cause
>               ExceptionDescription[] ed = new
> ExceptionAnalyzer().analyze(getException());
>               String rootCauseExceptionName = ed[ed.length -
> 1].getExceptionClassName();
>               // check for FlowSequenceException
>               flowSequenceError =
> FlowSequenceException.class.getName().equals(rootCauseExceptionName);
>           }
>       }
>       public boolean isFlowSequenceError() {
>           return flowSequenceError;
>       }
>       public Throwable getException() {
>           return exception;
>       }
>       public void setException(Throwable exception) {
>           this.exception = exception;
>       }
> }
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: tapestry-user-unsubscribe@jakarta.apache.org
> For additional commands, e-mail: tapestry-user-help@jakarta.apache.org
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: tapestry-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tapestry-user-help@jakarta.apache.org