From: fatefree <fat...@gm...> - 2011-07-22 14:49:02
|
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. |