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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]



Reply via email to