/* * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved. * * This file is part of the Jspresso framework. * * Jspresso is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Jspresso is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Jspresso. If not, see <http://www.gnu.org/licenses/>. */ package org.jspresso.framework.application.action; import java.util.Collection; import java.util.Map; import org.apache.commons.lang3.builder.ToStringBuilder; import org.jspresso.framework.action.IAction; import org.jspresso.framework.action.IActionHandler; import org.jspresso.framework.application.IController; import org.jspresso.framework.util.exception.NestedRuntimeException; import org.jspresso.framework.util.lang.StringUtils; /** * This is the base class for all application actions. It establishes the * foundation of the Jspresso action framework, i.e. each action can link : * <ul> * <li>a <i>wrapped</i> action that will execute a return back to the caller * action</li> * <li>a <i>next</i> action that will execute after the caller</li> * </ul> * The action chaining described above supports the separation of concerns that * consists in splitting the actions in two distinct categories : * <ul> * <li><i>frontend</i> actions that deal with user interaction. They are * typically used to bootstrap a service request from th UI, update the UI * state, trigger the display of information, errors, ...</li> * <li><i>backend</i> actions that are faceless, UI agnostic and deal with the * manipulation of the domain model, backend services, ...</li> * </ul> * Conceptually, a frontend action can call a backend action or another frontend * action but a backend action should stay on the backend, thus should only * reference another backend action. In other words, the backend layer should * never explicitly reference the frontend layer. * <p> * That's the main reason for having a <i>wrapped</i> and a <i>next</i> action * in the action chain. The <i>wrapped</i> action is perfectly suited to call * the backend layer (a backend action) and give the flow back to the frontend * layer. The <i>next</i> action is perfectly suited to chain 2 actions of the * same type (i.e. 2 frontend actions or 2 backend actions). Using this scheme * helps keeping layer dependencies clear. Of course, this dual chaining * mechanism is completely recursive thus allowing to compose small (generic) * actions into larger composite ones and promoting reuse. * <p> * Actions execute on a context (an arbitrary map of objects keyed by * "well-known" character strings) that is initialized by the controller when * the action is triggered. The context passes along the action chain and is * thus the perfect medium for actions to loosely communicate between each * other. There are some framework standard elements placed in the action * context that depend on the application state, but nothing prevents action * developers to synchronize on arbitrary custom context elements that would be * produced/consumed to/from the context. * <p> * Regarding framework-standard context elements, an action developer can * (should) leverage the utility methods declared in the * {@code AbstractActionContextAware} super class instead of relying on the * element keys in the map. Take a look to the * {@code AbstractActionContextAware} documentation to get your hands on * action context exploration. * <p> * Last but not least, you should be aware that actions should be coded with * thread-safety in mind. Actual action instances are generally singleton-like * (unless explicitly stated which is extremely rare) and thus, might be used by * multiple sessions (threads) at once. You should <b>never</b> use action class * attributes for argument passing along the action chain, but use the * thread-owned action execution context instead. The action context is a data * structure that is local to an action chain execution thus not shared across * threads (users). It is the perfect place for exchanging data between actions. * Of course, different instances of the same action can be used and configured * differently through their class attributes (refer to it as static * configuration), but this is all for using in different places in the * application. * * @author Vincent Vandenschrick */ @SuppressWarnings("UnusedParameters") public abstract class AbstractAction extends AbstractActionContextAware implements IAction { /** * {@code ACTION_MODEL_NAME}. */ protected static final String ACTION_MODEL_NAME = "ActionModel"; private String permId; private Collection<String> grantedRoles; private IAction nextAction; private IAction wrappedAction; /** * {@inheritDoc} */ @Override public boolean execute(IActionHandler actionHandler, Map<String, Object> context) { if (actionHandler.execute(getWrappedAction(context), context)) { return actionHandler.execute(getNextAction(context), context); } return false; } /** * Gets the permId. * * @return the permId. */ @Override public String getPermId() { return permId; } /** * {@inheritDoc} */ @Override public Collection<String> getGrantedRoles() { return grantedRoles; } /** * Sets the permanent identifier to this application element. Permanent * identifiers are used by different framework parts, like dynamic security or * record/replay controllers to uniquely identify an application element. * Permanent identifiers are generated by the SJS build based on the element * id but must be explicitly set if Spring XML is used. * * @param permId * the permId to set. */ @Override public void setPermId(String permId) { this.permId = permId; } /** * Assigns the roles that are authorized to execute this action. It supports * "<b>!</b>" prefix to negate the role(s). This will directly * influence the UI behaviour since unauthorized actions won't be displayed. * Setting the collection of granted roles to {@code null} (default * value) disables role based authorization on this action. Note that this * authorization enforcement does not prevent programmatic access that is of * the developer responsibility. * * @param grantedRoles * the grantedRoles to set. */ public void setGrantedRoles(Collection<String> grantedRoles) { this.grantedRoles = StringUtils.ensureSpaceFree(grantedRoles); } /** * Registers an action to be executed after this action and after the * <i>wrapped</i> one. This is perfectly suited to chain an action of the same * type (frontend or backend) as this one. * * @param nextAction * the next action to execute. */ public void setNextAction(IAction nextAction) { this.nextAction = nextAction; } /** * Registers an action to be executed after this action but before the * <i>next</i> one. This is perfectly suited to chain a backend action from a * frontend action since the control flow will return back to the calling * layer (the frontend). * * @param wrappedAction * the wrappedAction to set. */ public void setWrappedAction(IAction wrappedAction) { this.wrappedAction = wrappedAction; } /** * {@inheritDoc} */ @Override public String toString() { return new ToStringBuilder(this).toString(); } /** * Gets the controller (frontend or backend) out of the action context. * * @param context * the action context. * @return the controller (frontend or backend). */ protected abstract IController getController(Map<String, Object> context); /** * Gets the next action reference. If the next action has been configured * strongly through the setter method, it is directly returned. If not, it is * looked up into the action context. * * @param context * the action context. * @return the next action to execute. * @see #setNextAction(IAction) */ public IAction getNextAction(Map<String, Object> context) { return nextAction; } /** * Gets the wrapped action reference. If the wrapped action has been * configured strongly through the setter method, it is directly returned. If * not, it is looked up into the action context. * * @param context * the action context. * @return the wrapped action to execute. * @see #setWrappedAction(IAction) */ public IAction getWrappedAction(Map<String, Object> context) { return wrappedAction; } /** * Clones the action. * @return the action clone. */ @Override public AbstractAction clone() { try { return (AbstractAction) super.clone(); } catch (CloneNotSupportedException ex) { throw new NestedRuntimeException(ex); } } /** * The exception is re-thrown by default. * {@inheritDoc} * * @param ex * the exception that occurs. * @param context * the context where some extra information can be retrieved. * @return */ @Override public boolean handleException(Throwable ex, Map<String, Object> context) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else if (ex instanceof Error) { throw (Error) ex; } return false; } }