In order to prevent both old and duplicate page submission in JSF, I've been trying to implement a token-generating scheme by extending FormRenderer to output a randomly generated token into the h:form output. Then, I implemented a phase listener to compare this token with one stored on the server. If they do not match or that token is already found in a list of processed tokens, I am stopping the lifecycle and displaying an error message.
We're using JSF 1.1 and A4J 1.1.0 versions.
Everything is working fine for the extension of h:form (I extend FormRenderer), but when I try to extend AjaxFormRenderer (to account for a4j:form) I cannot find the correct configuration options to make it work without the following error:
My faces-config.xml looks as follows:
This faces-config works fine when extending FormRenderer, but not on a class extending AjaxFormRenderer. I've also tried using latest component-family and renderer-type based on RichFaces documentation to no avail. I can't find specific documentation on what these fields should read for A4J version 1.1.0.
I've also tried to implement with PhaseListeners where the PhaseListener writes the token to the page, but ViewRoot.getChildren is null before the phase, and after the phase the view has been rendered already. Is there a way to get around this in JSF 1.1? There is no f:phaseListener tag and ViewRoot has no addPhaseListener() method.
Any help anyone could offer would be much appreciated!
One of the problems with this concept is that - assuming you're doing what I think you're doing - it's all in vain.
If you're simply trying to avoid the common problem where people "buy something twice" because they get all impatient and double-click or triple-click the submit button, then generating a unique token on the render is in vain. The submit is based on the page in the user's browser, where the token has already been set, and all they'll end up doing is resubmitting the same token over and over.
But if the submit process is itself going to check the token, then you don't need complex JSF-specific cuteness. Just generate the token as an ordinary backing bean property and reference it in the View Form as a hidden text field.
The tricky part isn't rendering, it's when the form comes back to you. This is in part because page submits each come in on discrete threads, so if the user submits the form again when the first submit is still processing, a new thread will start in parallel with the old one. So at heart, it's a threading issue.
There are 2 primary ways to avoid that problem. One is to simply wrap all the work in a database transaction. That works especially well if the token is a generated transaction ID and the transaction is inserted, not updated (and for the record, it should be or hackers could hijack it). In that particular case, duplicate submits would simply bounce at the database level.
Another, more general way, is to create a session or application-scope object that's thread-safe and that manages outstanding transactions. Make it serialize requests and reject duplicates.
Most of the time, duplicate submits are going to be because someone spazzes out and multi-clicks the submit button. However, if the request has a significant processing delay, they may do an "oops" and change something. So it's a good idea to send back what was actually accepted so that the user can confirm it.
Customer surveys are for companies who didn't pay proper attention to begin with.