I cleaned up the code from my original post and included an actual working
example here. I made some changes such as synchronizing on session, and
combining the two resolution methods - subclasses can provide state if
different resolutions need to be returned. Also, I use googles MapMaker to
expire the formTokens.

public abstract class AbstractAction<T> implements Serializable {
    private String formToken;

    // Performs an action in response to an event.
    public Resolution act(final T actionBean) { 
        HttpSession session =
actionBean.getContext().getRequest().getSession();

        synchronized(session)
        {       
            // look for an existing action in session 
            Map<String, AbstractAction<T>> actionMap =
getActionMap(session);
            AbstractAction<T> action = actionMap.get(formToken);

            // if this is the first submission, the form is processed by the
subclass
            if(action == null) 
            { 
                doAction(actionBean); 
                actionMap.put(formToken, this);
                action = this; 
            } 

                        formToken = null; // clear this out so ajax forward 
resolutions dont
prepopulate it (requires BeanFirstPopulationStrategy)
            return action.getResolution(actionBean);
        }       
    } 

    // Gets the action map from session, or creates and stores it in session
if it was not found.
    @SuppressWarnings("unchecked")
    private Map<String, AbstractAction<T>> getActionMap(HttpSession session)
{
        Map<String, AbstractAction<T>> actionMap = (Map<String
,AbstractAction<T>>)session.getAttribute("AbstractAction.ActionSession");

        // if it was not found, create a new map and store it in session
        if(actionMap == null)
        {
            actionMap = new MapMaker().expiration(120,
TimeUnit.SECONDS).makeMap();
            session.setAttribute(ACTION_SESSION, actionMap);
        }
        
        return actionMap;
    }

    // Allows subclasses to process the action
    protected abstract void doAction(T actionBean); 

    // Returns the resolution as determined by the outcome of the action
    protected abstract Resolution getResolution(T actionBean);

    // getters and setters
    public String getFormToken() {
        if(formToken == null)
            formToken = UUID.randomUUID().toString();
        
        return formToken;
    }

    public void setFormToken(String formToken) {
        this.formToken = formToken;
    }
    
}

And here is an actual actionBean:

public class AddCategoryActionBean extends BaseActionBean {
    private Category category = new Category();
    private AddCategoryAction addCategoryAction = new AddCategoryAction();

    public Resolution addCategory() {
        return addCategoryAction.act(this);
    }

    // getters and setters
    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    public AddCategoryAction getAddCategoryAction() {
        return addCategoryAction;
    }

    public void setAddCategoryAction(AddCategoryAction addCategoryAction) {
        this.addCategoryAction = addCategoryAction;
    }
    
    // AddCategoryAction to handle the submission
    private static class AddCategoryAction extends
AbstractAction<AddCategoryActionBean> {
        // optional state can be stored here from doAction for getResolution
to use
        
        @Override
        protected void doAction(AddCategoryActionBean actionBean) {
            CategoryService.saveCategory(actionBean.category);
        }

        @Override
        protected Resolution getResolution(AddCategoryActionBean actionBean)
{ 
            actionBean.getContext().getMessages().add("This message still
shows on duplicate submissions");
            return new RedirectResolution(TaskTrackerTasksActionBean.class);
        }

    }
    
}

The form is as you would expect, except for a hidden tag <s:hidden
name="addCategoryAction.formToken"/>. It works like a charm, and doesn't
seem like too much of a burden with the AddCategoryAction class considering
the protection you get from using it. I plan to enhance the AbstractAction
as well to provide some sort of XSRF protection with a separate token.

The only real catch is that any optional state in an action subclass will be
stored in session, so its best to keep it light. Anyway, feel free to play
it with it if it might take care of your double submit needs.
-- 
View this message in context: 
http://old.nabble.com/An-alternative-solution-to-handle-double-form-submissions.-Feedback--tp32111253p32115881.html
Sent from the stripes-users mailing list archive at Nabble.com.


------------------------------------------------------------------------------
10 Tips for Better Web Security
Learn 10 ways to better secure your business today. Topics covered include:
Web security, SSL, hacker attacks & Denial of Service (DoS), private keys,
security Microsoft Exchange, secure Instant Messaging, and much more.
http://www.accelacomm.com/jaw/sfnl/114/51426210/
_______________________________________________
Stripes-users mailing list
Stripes-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/stripes-users

Reply via email to