Description
An interceptor that copies all the properties of every object in the value stack to the currently executing object, except for any object that implements Unchainable. A collection of optional includes
and excludes
may be provided to control how and which parameters are copied. Only includes
or excludes
may be specified. Specifying both results in undefined behavior. See the javadocs for ReflectionProvider#copy(Object, Object, java.util.Map, java.util.Collection, java.util.Collection) for more information.
Note |
---|
It is important to remember that this interceptor does nothing if there are no objects already on the stack. This means two things: - you can safely apply it to all your actions without any worry of adverse affects.
- it is up to you to ensure an object exists in the stack prior to invoking this action. The most typical way this is done is through the use of the <b>chain</b> result type, which combines with this interceptor to make up the action chaining feature.
|
By default Errors, Field errors and Message aren't copied during chaining, to change the behaviour you can specify the below three constants in struts.properties or struts.xml:
struts.xwork.chaining.copyErrors
- set to true to copy Action Errorsstruts.xwork.chaining.copyFieldErrors
- set to true to copy Field Errorsstruts.xwork.chaining.copyMessages
- set to true to copy Action Messages
Example:
Code Block |
---|
|
<constant name="struts.xwork.chaining.copyErrors" value="true"/> |
Parameters
excludes
(optional) - the list of parameter names to exclude from copying (all others will be included)includes
(optional) - the list of parameter names to include when copying (all others will be excluded)
Extending the Interceptor
There are no known extension points to this interceptor.
Examples
Simple example how to chain two actions
Code Block |
---|
|
<action name="someAction" class="com.examples.SomeAction">
|
Wiki Markup |
---|
{snippet:id=description|javadoc=true|url=com.opensymphony.xwork.interceptor.ChainingInterceptor} |
Parameters
Wiki Markup |
---|
{snippet:id=parameters|javadoc=true|url=com.opensymphony.xwork.interceptor.ChainingInterceptor} |
Extending the Interceptor
Wiki Markup |
---|
{snippet:id=extending|javadoc=true|url=com.opensymphony.xwork.interceptor.ChainingInterceptor} |
Examples
Wiki Markup |
---|
{snippet:id=example|lang=xml|javadoc=true|url=com.opensymphony.xwork.interceptor.ChainingInterceptor} |
How to use the Chaining Interceptor
The following code snippet shows how interceptor stacks work for chaining. If someone wants to post xwork.xml (and more complex) examples it would be appreciated ![(smile)](/confluence/s/z2xvrl/8804/1naq7q0/_/images/icons/emoticons/smile.svg)
Code Block |
---|
Interceptors-stack A before
Action A
Interceptor-stack B before
Action B
Action B result
Interceptor-stack B after
Interceptor-stack A after
|
Interceptors that wrap Chained Actions
Sometimes you may want to have an interceptor that wraps a number of chained actions (and is included in the Interceptor stack for each), but is only invoked at the start and end of the chain. For example, an Interceptor that manages a Hibernate Session / Transaction. Here is an example from my 'teach-myself-webwork-and-hibernate project named 'cash' after Johnny Cash.
Code Block |
---|
<interceptor name="hibernate" class="cash.interceptor.HibernateInterceptor"/>
<interceptor name="login" class="cash.interceptor.LoginInterceptor"/>
<interceptor-stack name="cashDefaultStack">
<interceptor-ref name="defaultStackbasicStack"/>
<interceptor-ref <result name="componentsuccess"/>
<interceptor-ref name type="hibernatechain">otherAction</>
<interceptor-ref name="login"/>
</interceptor-stack>
<interceptor-stackresult>
</action>
<action name="cashValidationWorkflowStackotherAction">
<interceptor-ref name="cashDefaultStack"/ class="com.examples.OtherAction">
<interceptor-ref name="validationchain"/>
<interceptor-ref name="workflowbasicStack"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref <result name="cashDefaultStacksuccess">good_result.ftl</>
result>
</action> |
This examples chains two actions but only one property from first action is copied to another
Code Block |
---|
|
<action name="listsomeAction" class="cashcom.actionexamples.SelectUserActionSomeAction">
<interceptor-ref name="basicStack"/>
<result name="success" type="dispatcherchain">list.vm<>otherAction</result>
</action>
<action name="editotherAction" class="cashcom.actionexamples.EditActionOtherAction">
<result name="success" type="chain">list</result>
<result name="input" type="dispatcher">edit.vm</result>
<interceptor-ref name="cashValidationWorkflowStackchainStack"/>
</action>
|
In this example, after editing a user, the EditAction is chained to the ListAction to display a list of all users to the screen.
We want the following
Code Block |
---|
EditActionInterceptorStack - before
EditAction
ListActionInterceptorStack (except for Hibernate) - before
ListAction
ListActionInterceptorStack (except for Hibernate) - end
EditActinInterceptorStack - end
|
But instead we get:
Code Block |
---|
EditActionInterceptorStack - before
EditAction
ListActionInterceptorStack (including Hibernate) - before
ListAction
ListActionInterceptorStack (including Hibernate) - end
EditActinInterceptorStack - end ERROR! Hibernate Session / Transaction is already closed!!!
|
The way to get the desired behaviour is to either not use a chained action (and incorporate the ListAction logic into EditAction, or to make the HibernateInterceptor smart enough to handle chained actions.
The HibernateInterceptor can use a ThreadLocal to hold state and detect if it is inside a chain. When it detects this, it can do nothing.
Code Block |
---|
package cash.interceptor;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Transaction;
import org.apache.log4j.Logger;
import com.opensymphony.xwork.Action;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.interceptor.Interceptor;
import cash.action.HibernateAction;
import cash.util.HibernateUtil;
/**
* @author Gavin King
* @author Joel Hockey
* @version $Id: $
*/
public class HibernateInterceptor implements Interceptor {
private static final Logger LOG = Logger.getLogger(HibernateInterceptor.class);
private static ThreadLocal s_threadLocal = new ThreadLocal();
/** destroy */
public void destroy() { }
/** init */
public void init() { }
/** implement intercept */
public String intercept(ActionInvocation invocation) throws Exception {
LOG.debug("HibernateInterceptor called");
Action action = invocation.getAction();
if (!(action instanceof HibernateAction)) { return invocation.invoke(); }
// continue with HibernateAction
HibernateAction ha = (HibernateAction)action;
// if this interceptor is being chained, then transaction will already exist
// in that case, we should let the outer interceptor dispose of the sesion
boolean inChainedAction = true;
Transaction transaction = (Transaction)s_threadLocal.get();
if (transaction == null) {
inChainedAction = false;
transaction = HibernateUtil.currentSession().beginTransaction();
s_threadLocal.set(transaction);
}
boolean rollback = false;
try {
return invocation.invoke();
} catch (Exception e) {
// Note that all the cleanup is done
// after the view is rendered, so we
// have an open session in the view
rollback = true;
if (e instanceof HibernateException) {
LOG.error("HibernateException in execute()", e);
return HibernateAction.DBERROR;
} else {
LOG.error("Exception in execute()", e);
throw e;
}
} finally {
try {
if (!inChainedAction) {
s_threadLocal.set(null);
disposeSession(transaction, ha.getRollback() || rollback);
}
} catch (HibernateException e) {
LOG.error("HibernateException in dispose()", e);
return HibernateAction.DBERROR;
}
}
}
/** dispose of session */
public void disposeSession(Transaction transaction, boolean rollback) throws HibernateException {
LOG.debug("disposing");
if (!HibernateUtil.currentSession().isConnected()) {
LOG.debug("Session has already been disposed of - this will happen in a chained action");
return;
}
try {
if (transaction != null) {
if (rollback) {
LOG.debug("rolling back");
transaction.rollback();
} else {
LOG.debug("committing");
transaction.commit();
}
}
} catch (HibernateException e) {
LOG.error("error during commit/rollback", e);
if (!rollback && transaction != null) {
LOG.error("rolling back affter previous attempt to commit");
transaction.rollback();
}
throw e;
} finally {
HibernateUtil.closeSession();
}
}
}
|
...
<param name="chain.includes">prop1</param>
</interceptor-ref>
<result name="success">good_result.ftl</result>
</action> |