diff --git a/xwork-core/pom.xml b/xwork-core/pom.xml
new file mode 100644
index 000000000..ddc5e5c87
--- /dev/null
+++ b/xwork-core/pom.xml
@@ -0,0 +1,252 @@
+
+
+
+ 4.0.0
+ com.opensymphony
+ xwork-core
+ jar
+ XWork: Core
+
+
+ com.opensymphony
+ xwork-parent
+ 2.1.7-SNAPSHOT
+
+
+
+ scm:svn:http://svn.opensymphony.com/svn/xwork/trunk/core
+
+ scm:svn:https://svn.opensymphony.com/svn/xwork/trunk/core
+
+ https://svn.opensymphony.com/svn/xwork/trunk/core
+
+
+
+
+ The OpenSymphony Software License 1.1
+ ../src/resources/LICENSE.txt
+
+ This license is derived and fully compatible with the Apache Software
+ License - see http://www.apache.org/LICENSE.txt
+
+
+
+
+
+
+
+ j4
+
+
+
+ org.codehaus.mojo
+ retrotranslator-maven-plugin
+ 1.0-alpha-4
+
+
+ package
+
+ translate-project
+
+
+ false
+ true
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+ ${basedir}/src/main/java
+ ${basedir}/src/test/java
+
+
+ ${basedir}/src/main/resources
+
+
+
+
+ ${basedir}/src/test/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.4.2
+
+ false
+
+ ${project.build.testOutputDirectory}/xwork-jar.jar
+ ${project.build.testOutputDirectory}/xwork-zip.zip
+ ${project.build.testOutputDirectory}/xwork - jar.jar
+ ${project.build.testOutputDirectory}/xwork - zip.zip
+
+
+ **/*Test.java
+
+
+ **/XWorkTestCase.java
+ **/TestBean.java
+ **/TestBean2.java
+ **/TestInterceptor.java
+ **/AnnotatedTestBean.java
+ **/ContainerImplTest.java
+ **/URLUtilTest.java
+
+
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+
+
+
+ clean
+
+
+
+
+
+ maven-jar-plugin
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 1.2
+
+
+ package
+
+ shade
+
+
+ xwork-core
+
+
+ junit:junit
+ commons-logging:commons-logging
+ opensymphony:ognl
+ ognl:ognl
+ jboss:javassist
+ org.springframework:spring-core
+ org.springframework:spring-aop
+ org.springframework:spring-aspects
+ org.springframework:spring-beans
+ org.springframework:spring-context
+ org.springframework:spring-context-support
+ org.springframework:spring-web
+ org.springframework:spring-test
+ mockobjects:mockobjects-core
+ org.easymock:easymock
+ aopalliance:aopalliance
+ aspectwerkz:aspectwerkz-core
+ org.aspectj:aspectjrt
+ org.aspectj:aspectjweaver
+ cglib:cglib
+ cglib:cglib-nodep
+ asm:asm-util
+ org.testng:testng:jdk15
+
+
+
+
+ commons-lang:commons-lang
+
+ org/apache/commons/lang/StringUtils.class
+ org/apache/commons/lang/math/NumberUtils.class
+ org/apache/commons/lang/ObjectUtils*.class
+ org/apache/commons/lang/StringEscapeUtils.class
+ org/apache/commons/lang/exception/NestableRuntimeException.class
+ org/apache/commons/lang/exception/Nestable.class
+ org/apache/commons/lang/Entities*class
+ org/apache/commons/lang/UnhandledException.class
+ org/apache/commons/lang/IntHashMap*class
+
+
+
+
+
+ org.objectweb.asm
+ org.objectweb.asm.xwork
+
+
+ org.apache.commons.lang
+ org.apache.commons.lang.xwork
+
+
+
+
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ org.apache.commons.lang.xwork.*,com.opensymphony.xwork2.*
+
+
+
+
+ bundle-manifest
+ process-classes
+
+ manifest
+
+
+
+
+
+ install
+
+
+
+
+
+
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Action.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Action.java
new file mode 100644
index 000000000..f10a431b6
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Action.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+/**
+ * All actions may implement this interface, which exposes the execute() method.
+ *
+ * However, as of XWork 1.1, this is not required and is only here to assist users. You are free to create POJOs
+ * that honor the same contract defined by this interface without actually implementing the interface.
+ */
+public interface Action {
+
+ /**
+ * The action execution was successful. Show result
+ * view to the end user.
+ */
+ public static final String SUCCESS = "success";
+
+ /**
+ * The action execution was successful but do not
+ * show a view. This is useful for actions that are
+ * handling the view in another fashion like redirect.
+ */
+ public static final String NONE = "none";
+
+ /**
+ * The action execution was a failure.
+ * Show an error view, possibly asking the
+ * user to retry entering data.
+ */
+ public static final String ERROR = "error";
+
+ /**
+ * The action execution require more input
+ * in order to succeed.
+ * This result is typically used if a form
+ * handling action has been executed so as
+ * to provide defaults for a form. The
+ * form associated with the handler should be
+ * shown to the end user.
+ *
+ * This result is also used if the given input
+ * params are invalid, meaning the user
+ * should try providing input again.
+ */
+ public static final String INPUT = "input";
+
+ /**
+ * The action could not execute, since the
+ * user most was not logged in. The login view
+ * should be shown.
+ */
+ public static final String LOGIN = "login";
+
+
+ /**
+ * Where the logic of the action is executed.
+ *
+ * @return a string representing the logical result of the execution.
+ * See constants in this interface for a list of standard result values.
+ * @throws Exception thrown if a system level exception occurs.
+ * Note: Application level exceptions should be handled by returning
+ * an error value, such as Action.ERROR.
+ */
+ public String execute() throws Exception;
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java
new file mode 100644
index 000000000..170c56796
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.util.*;
+
+
+/**
+*
+*
+* This result invokes an entire other action, complete with it's own interceptor stack and result.
+*
+*
+*
+* This result type takes the following parameters:
+*
+*
+*
+*
+*
+* actionName (default) - the name of the action that will be chained to
+*
+* namespace - used to determine which namespace the Action is in that we're chaining. If namespace is null,
+* this defaults to the current namespace
+*
+* method - used to specify another method on target action to be invoked.
+* If null, this defaults to execute method
+*
+* skipActions - (optional) the list of comma separated action names for the
+* actions that could be chained to
+*
+*
+*
+*
+*
+* Example:
+*
+*
+* <package name="public" extends="struts-default">
+* <!-- Chain creatAccount to login, using the default parameter -->
+* <action name="createAccount" class="...">
+* <result type="chain">login</result>
+* </action>
+*
+* <action name="login" class="...">
+* <!-- Chain to another namespace -->
+* <result type="chain">
+* <param name="actionName">dashboard</param>
+* <param name="namespace">/secure</param>
+* </result>
+* </action>
+* </package>
+*
+* <package name="secure" extends="struts-default" namespace="/secure">
+* <action name="dashboard" class="...">
+* <result>dashboard.jsp</result>
+* </action>
+* </package>
+*
+*
+* @author Alexandru Popescu
+*/
+public class ActionChainResult implements Result {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ActionChainResult.class);
+
+ /**
+ * The result parameter name to set the name of the action to chain to.
+ */
+ public static final String DEFAULT_PARAM = "actionName";
+
+ /**
+ * The action context key to save the chain history.
+ */
+ private static final String CHAIN_HISTORY = "CHAIN_HISTORY";
+
+ /**
+ * The result parameter name to set the name of the action to chain to.
+ */
+ public static final String SKIP_ACTIONS_PARAM = "skipActions";
+
+
+ private ActionProxy proxy;
+ private String actionName;
+
+ private String namespace;
+
+ private String methodName;
+
+ /**
+ * The list of actions to skip.
+ */
+ private String skipActions;
+
+ private ActionProxyFactory actionProxyFactory;
+
+ public ActionChainResult() {
+ super();
+ }
+
+ public ActionChainResult(String namespace, String actionName, String methodName) {
+ this.namespace = namespace;
+ this.actionName = actionName;
+ this.methodName = methodName;
+ }
+
+ public ActionChainResult(String namespace, String actionName, String methodName, String skipActions) {
+ this.namespace = namespace;
+ this.actionName = actionName;
+ this.methodName = methodName;
+ this.skipActions = skipActions;
+ }
+
+
+ /**
+ * @param actionProxyFactory the actionProxyFactory to set
+ */
+ @Inject
+ public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
+ this.actionProxyFactory = actionProxyFactory;
+ }
+
+ /**
+ * Set the action name.
+ *
+ * @param actionName The action name.
+ */
+ public void setActionName(String actionName) {
+ this.actionName = actionName;
+ }
+
+ /**
+ * sets the namespace of the Action that we're chaining to. if namespace
+ * is null, this defaults to the current namespace.
+ *
+ * @param namespace the name of the namespace we're chaining to
+ */
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ /**
+ * Set the list of actions to skip.
+ * To test if an action should not throe an infinite recursion,
+ * only the action name is used, not the namespace.
+ *
+ * @param actions The list of action name separated by a white space.
+ */
+ public void setSkipActions(String actions) {
+ this.skipActions = actions;
+ }
+
+
+ public void setMethod(String method) {
+ this.methodName = method;
+ }
+
+ public ActionProxy getProxy() {
+ return proxy;
+ }
+
+ /**
+ * Get the XWork chain history.
+ * The stack is a list of namespace/action!method keys.
+ */
+ public static LinkedList getChainHistory() {
+ LinkedList chainHistory = (LinkedList) ActionContext.getContext().get(CHAIN_HISTORY);
+ // Add if not exists
+ if (chainHistory == null) {
+ chainHistory = new LinkedList();
+ ActionContext.getContext().put(CHAIN_HISTORY, chainHistory);
+ }
+
+ return chainHistory;
+ }
+
+ /**
+ * @param invocation the DefaultActionInvocation calling the action call stack
+ */
+ public void execute(ActionInvocation invocation) throws Exception {
+ // if the finalNamespace wasn't explicitly defined, assume the current one
+ if (this.namespace == null) {
+ this.namespace = invocation.getProxy().getNamespace();
+ }
+
+ ValueStack stack = ActionContext.getContext().getValueStack();
+ String finalNamespace = TextParseUtil.translateVariables(namespace, stack);
+ String finalActionName = TextParseUtil.translateVariables(actionName, stack);
+ String finalMethodName = this.methodName != null
+ ? TextParseUtil.translateVariables(this.methodName, stack)
+ : null;
+
+ if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) {
+ addToHistory(finalNamespace, finalActionName, finalMethodName);
+ throw new XWorkException("Infinite recursion detected: "
+ + ActionChainResult.getChainHistory().toString());
+ }
+
+ if (ActionChainResult.getChainHistory().isEmpty() && invocation != null && invocation.getProxy() != null) {
+ addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod());
+ }
+ addToHistory(finalNamespace, finalActionName, finalMethodName);
+
+ HashMap extraContext = new HashMap();
+ extraContext.put(ActionContext.VALUE_STACK, ActionContext.getContext().getValueStack());
+ extraContext.put(ActionContext.PARAMETERS, ActionContext.getContext().getParameters());
+ extraContext.put(CHAIN_HISTORY, ActionChainResult.getChainHistory());
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Chaining to action " + finalActionName);
+ }
+
+ proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext);
+ proxy.execute();
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final ActionChainResult that = (ActionChainResult) o;
+
+ if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) return false;
+ if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false;
+ if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false;
+
+ return true;
+ }
+
+ @Override public int hashCode() {
+ int result;
+ result = (actionName != null ? actionName.hashCode() : 0);
+ result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
+ result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
+ return result;
+ }
+
+ private boolean isInChainHistory(String namespace, String actionName, String methodName) {
+ LinkedList extends String> chainHistory = ActionChainResult.getChainHistory();
+
+ if (chainHistory == null) {
+ return false;
+ } else {
+ // Actions to skip
+ Set skipActionsList = new HashSet();
+ if (skipActions != null && skipActions.length() > 0) {
+ ValueStack stack = ActionContext.getContext().getValueStack();
+ String finalSkipActions = TextParseUtil.translateVariables(this.skipActions, stack);
+ skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions));
+ }
+ if (!skipActionsList.contains(actionName)) {
+ // Get if key is in the chain history
+ return chainHistory.contains(makeKey(namespace, actionName, methodName));
+ }
+
+ return false;
+ }
+ }
+
+ private void addToHistory(String namespace, String actionName, String methodName) {
+ List chainHistory = ActionChainResult.getChainHistory();
+ chainHistory.add(makeKey(namespace, actionName, methodName));
+ }
+
+ private String makeKey(String namespace, String actionName, String methodName) {
+ if (null == methodName) {
+ return namespace + "/" + actionName;
+ }
+
+ return namespace + "/" + actionName + "!" + methodName;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionContext.java
new file mode 100644
index 000000000..c8ed44b1e
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionContext.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+
+/**
+ * The ActionContext is the context in which an {@link Action} is executed. Each context is basically a
+ * container of objects an action needs for execution like the session, parameters, locale, etc.
+ *
+ * The ActionContext is thread local which means that values stored in the ActionContext are
+ * unique per thread. See the {@link ThreadLocal} class for more information. The benefit of
+ * this is you don't need to worry about a user specific action context, you just get it:
+ *
+ * ActionContext context = ActionContext.getContext();
+ *
+ * Finally, because of the thread local usage you don't need to worry about making your actions thread safe.
+ *
+ * @author Patrick Lightbody
+ * @author Bill Lynch (docs)
+ */
+public class ActionContext implements Serializable {
+ static ThreadLocal actionContext = new ThreadLocal();
+
+ /**
+ * Constant that indicates the action is running under a "development mode".
+ * This mode provides more feedback that is useful for developers but probably
+ * too verbose/error prone for production.
+ */
+ //public static final String DEV_MODE = "__devMode";
+
+ /**
+ * Constant for the name of the action being executed.
+ */
+ public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name";
+
+ /**
+ * Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}.
+ */
+ public static final String VALUE_STACK = ValueStack.VALUE_STACK;
+
+ /**
+ * Constant for the action's session.
+ */
+ public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session";
+
+ /**
+ * Constant for the action's application context.
+ */
+ public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application";
+
+ /**
+ * Constant for the action's parameters.
+ */
+ public static final String PARAMETERS = "com.opensymphony.xwork2.ActionContext.parameters";
+
+ /**
+ * Constant for the action's locale.
+ */
+ public static final String LOCALE = "com.opensymphony.xwork2.ActionContext.locale";
+
+ /**
+ * Constant for the action's type converter.
+ */
+ public static final String TYPE_CONVERTER = "com.opensymphony.xwork2.ActionContext.typeConverter";
+
+ /**
+ * Constant for the action's {@link com.opensymphony.xwork2.ActionInvocation invocation} context.
+ */
+ public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation";
+
+ /**
+ * Constant for the map of type conversion errors.
+ */
+ public static final String CONVERSION_ERRORS = "com.opensymphony.xwork2.ActionContext.conversionErrors";
+
+
+ /**
+ * Constant for the container
+ */
+ public static final String CONTAINER = "com.opensymphony.xwork2.ActionContext.container";
+
+ Map context;
+
+
+ /**
+ * Creates a new ActionContext initialized with another context.
+ *
+ * @param context a context map.
+ */
+ public ActionContext(Map context) {
+ this.context = context;
+ }
+
+
+ /**
+ * Sets the action invocation (the execution state).
+ *
+ * @param actionInvocation the action execution state.
+ */
+ public void setActionInvocation(ActionInvocation actionInvocation) {
+ put(ACTION_INVOCATION, actionInvocation);
+ }
+
+ /**
+ * Gets the action invocation (the execution state).
+ *
+ * @return the action invocation (the execution state).
+ */
+ public ActionInvocation getActionInvocation() {
+ return (ActionInvocation) get(ACTION_INVOCATION);
+ }
+
+ /**
+ * Sets the action's application context.
+ *
+ * @param application the action's application context.
+ */
+ public void setApplication(Map application) {
+ put(APPLICATION, application);
+ }
+
+ /**
+ * Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise.
+ *
+ * @return a Map of ServletContext or generic application level Map
+ */
+ public Map getApplication() {
+ return (Map) get(APPLICATION);
+ }
+
+ /**
+ * Sets the action context for the current thread.
+ *
+ * @param context the action context.
+ */
+ public static void setContext(ActionContext context) {
+ actionContext.set(context);
+ }
+
+ /**
+ * Returns the ActionContext specific to the current thread.
+ *
+ * @return the ActionContext for the current thread, is never null .
+ */
+ public static ActionContext getContext() {
+ return (ActionContext) actionContext.get();
+
+ // Don't do lazy context creation, as it requires container; the creation of which may
+ // precede the context creation
+ //if (context == null) {
+ // ValueStack vs = ValueStackFactory.getFactory().createValueStack();
+ // context = new ActionContext(vs.getContext());
+ // setContext(context);
+ //}
+
+ }
+
+ /**
+ * Sets the action's context map.
+ *
+ * @param contextMap the context map.
+ */
+ public void setContextMap(Map contextMap) {
+ getContext().context = contextMap;
+ }
+
+ /**
+ * Gets the context map.
+ *
+ * @return the context map.
+ */
+ public Map getContextMap() {
+ return context;
+ }
+
+ /**
+ * Sets conversion errors which occurred when executing the action.
+ *
+ * @param conversionErrors a Map of errors which occurred when executing the action.
+ */
+ public void setConversionErrors(Map conversionErrors) {
+ put(CONVERSION_ERRORS, conversionErrors);
+ }
+
+ /**
+ * Gets the map of conversion errors which occurred when executing the action.
+ *
+ * @return the map of conversion errors which occurred when executing the action or an empty map if
+ * there were no errors.
+ */
+ public Map getConversionErrors() {
+ Map errors = (Map) get(CONVERSION_ERRORS);
+
+ if (errors == null) {
+ errors = new HashMap();
+ setConversionErrors(errors);
+ }
+
+ return errors;
+ }
+
+ /**
+ * Sets the Locale for the current action.
+ *
+ * @param locale the Locale for the current action.
+ */
+ public void setLocale(Locale locale) {
+ put(LOCALE, locale);
+ }
+
+ /**
+ * Gets the Locale of the current action. If no locale was ever specified the platform's
+ * {@link java.util.Locale#getDefault() default locale} is used.
+ *
+ * @return the Locale of the current action.
+ */
+ public Locale getLocale() {
+ Locale locale = (Locale) get(LOCALE);
+
+ if (locale == null) {
+ locale = Locale.getDefault();
+ setLocale(locale);
+ }
+
+ return locale;
+ }
+
+ /**
+ * Sets the name of the current Action in the ActionContext.
+ *
+ * @param name the name of the current action.
+ */
+ public void setName(String name) {
+ put(ACTION_NAME, name);
+ }
+
+ /**
+ * Gets the name of the current Action.
+ *
+ * @return the name of the current action.
+ */
+ public String getName() {
+ return (String) get(ACTION_NAME);
+ }
+
+ /**
+ * Sets the action parameters.
+ *
+ * @param parameters the parameters for the current action.
+ */
+ public void setParameters(Map parameters) {
+ put(PARAMETERS, parameters);
+ }
+
+ /**
+ * Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of
+ * parameters otherwise.
+ *
+ * @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a
+ * generic Map of parameters otherwise.
+ */
+ public Map getParameters() {
+ return (Map) get(PARAMETERS);
+ }
+
+ /**
+ * Sets a map of action session values.
+ *
+ * @param session the session values.
+ */
+ public void setSession(Map session) {
+ put(SESSION, session);
+ }
+
+ /**
+ * Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
+ *
+ * @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
+ */
+ public Map getSession() {
+ return (Map) get(SESSION);
+ }
+
+ /**
+ * Sets the OGNL value stack.
+ *
+ * @param stack the OGNL value stack.
+ */
+ public void setValueStack(ValueStack stack) {
+ put(VALUE_STACK, stack);
+ }
+
+ /**
+ * Gets the OGNL value stack.
+ *
+ * @return the OGNL value stack.
+ */
+ public ValueStack getValueStack() {
+ return (ValueStack) get(VALUE_STACK);
+ }
+
+ /**
+ * Gets the container for this request
+ *
+ * @param cont The container
+ */
+ public void setContainer(Container cont) {
+ put(CONTAINER, cont);
+ }
+
+ /**
+ * Sets the container for this request
+ *
+ * @return The container
+ */
+ public Container getContainer() {
+ return (Container) get(CONTAINER);
+ }
+
+ public T getInstance(Class type) {
+ Container cont = getContainer();
+ if (cont != null) {
+ return cont.getInstance(type);
+ } else {
+ throw new XWorkException("Cannot find an initialized container for this request.");
+ }
+ }
+
+ /**
+ * Returns a value that is stored in the current ActionContext by doing a lookup using the value's key.
+ *
+ * @param key the key used to find the value.
+ * @return the value that was found using the key or null if the key was not found.
+ */
+ public Object get(String key) {
+ return context.get(key);
+ }
+
+ /**
+ * Stores a value in the current ActionContext. The value can be looked up using the key.
+ *
+ * @param key the key of the value.
+ * @param value the value to be stored.
+ */
+ public void put(String key, Object value) {
+ context.put(key, value);
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
new file mode 100644
index 000000000..58c992a51
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ * Provides hooks for handling key action events
+ */
+public interface ActionEventListener {
+ /**
+ * Called after an action has been created.
+ *
+ * @param action The action
+ * @param stack The current value stack
+ * @return The action to use
+ */
+ public Object prepare(Object action, ValueStack stack);
+
+ /**
+ * Called when an exception is thrown by the action
+ *
+ * @param t The exception/error that was thrown
+ * @param stack The current value stack
+ * @return A result code to execute, can be null
+ */
+ public String handleException(Throwable t, ValueStack stack);
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
new file mode 100644
index 000000000..4154a0afb
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.interceptor.PreResultListener;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.io.Serializable;
+
+
+/**
+ * An {@link ActionInvocation} represents the execution state of an {@link Action}. It holds the Interceptors and the Action instance.
+ * By repeated re-entrant execution of the invoke() method, initially by the {@link ActionProxy}, then by the Interceptors, the
+ * Interceptors are all executed, and then the {@link Action} and the {@link Result}.
+ *
+ * @author Jason Carreira
+ * @see com.opensymphony.xwork2.ActionProxy
+ */
+public interface ActionInvocation extends Serializable {
+
+ /**
+ * Get the Action associated with this ActionInvocation.
+ *
+ * @return the Action
+ */
+ Object getAction();
+
+ /**
+ * Gets whether this ActionInvocation has executed before.
+ * This will be set after the Action and the Result have executed.
+ *
+ * @return true if this ActionInvocation has executed before.
+ */
+ boolean isExecuted();
+
+ /**
+ * Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
+ * responsible for setting this ActionContext onto the ThreadLocal before invoking
+ * the ActionInvocation and resetting the old ActionContext afterwards.
+ *
+ * @return the ActionContext.
+ */
+ ActionContext getInvocationContext();
+
+ /**
+ * Get the ActionProxy holding this ActionInvocation.
+ *
+ * @return the ActionProxy.
+ */
+ ActionProxy getProxy();
+
+ /**
+ * If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
+ * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
+ * ActionInvocation's result has not been executed before, the Result instance will be created and populated with
+ * the result params.
+ *
+ * @return the result.
+ * @throws Exception can be thrown.
+ */
+ Result getResult() throws Exception;
+
+ /**
+ * Gets the result code returned from this ActionInvocation.
+ *
+ * @return the result code
+ */
+ String getResultCode();
+
+ /**
+ * Sets the result code, possibly overriding the one returned by the
+ * action.
+ *
+ * The "intended" purpose of this method is to allow PreResultListeners to
+ * override the result code returned by the Action.
+ *
+ * If this method is used before the Action executes, the Action's returned
+ * result code will override what was set. However the Action could (if
+ * specifically coded to do so) inspect the ActionInvocation to see that
+ * someone "upstream" (e.g. an Interceptor) had suggested a value as the
+ * result, and it could therefore return the same value itself.
+ *
+ * If this method is called between the Action execution and the Result
+ * execution, then the value set here will override the result code the
+ * action had returned. Creating an Interceptor that implements
+ * {@link PreResultListener} will give you this oportunity.
+ *
+ * If this method is called after the Result has been executed, it will
+ * have the effect of raising an IllegalStateException.
+ *
+ * @param resultCode the result code.
+ * @throws IllegalStateException if called after the Result has been executed.
+ * @see #isExecuted()
+ */
+ void setResultCode(String resultCode);
+
+ /**
+ * Gets the ValueStack associated with this ActionInvocation.
+ *
+ * @return the ValueStack
+ */
+ ValueStack getStack();
+
+ /**
+ * Register a {@link PreResultListener} to be notified after the Action is executed and
+ * before the Result is executed.
+ *
+ * The ActionInvocation implementation must guarantee that listeners will be called in
+ * the order in which they are registered.
+ *
+ * Listener registration and execution does not need to be thread-safe.
+ *
+ * @param listener the listener to add.
+ */
+ void addPreResultListener(PreResultListener listener);
+
+ /**
+ * Invokes the next step in processing this ActionInvocation.
+ *
+ * If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit
+ * ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor
+ * to execute. If there are no more Interceptors to be applied, the Action is executed.
+ * If the {@link ActionProxy#getExecuteResult()} method returns true , the Result is also executed.
+ *
+ * @throws Exception can be thrown.
+ * @return the return code.
+ */
+ String invoke() throws Exception;
+
+ /**
+ * Invokes only the Action (not Interceptors or Results).
+ *
+ * This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
+ * being manipulated for certain functionality.
+ *
+ * @return the return code.
+ * @throws Exception can be thrown.
+ */
+ String invokeActionOnly() throws Exception;
+
+ /**
+ * Sets the action event listener to respond to key action events.
+ *
+ * @param listener the listener.
+ */
+ void setActionEventListener(ActionEventListener listener);
+
+ void init(ActionProxy proxy) ;
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxy.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
new file mode 100644
index 000000000..916fef387
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+
+/**
+ * ActionProxy is an extra layer between XWork and the action so that different proxies are possible.
+ *
+ * An example of this would be a remote proxy, where the layer between XWork and the action might be RMI or SOAP.
+ *
+ * @author Jason Carreira
+ */
+public interface ActionProxy {
+
+ /**
+ * Gets the Action instance for this Proxy.
+ *
+ * @return the Action instance
+ */
+ Object getAction();
+
+ /**
+ * Gets the alias name this ActionProxy is mapped to.
+ *
+ * @return the alias name
+ */
+ String getActionName();
+
+ /**
+ * Gets the ActionConfig this ActionProxy is built from.
+ *
+ * @return the ActionConfig
+ */
+ ActionConfig getConfig();
+
+ /**
+ * Sets whether this ActionProxy should also execute the Result after executing the Action.
+ *
+ * @param executeResult true to also execute the Result.
+ */
+ void setExecuteResult(boolean executeResult);
+
+ /**
+ * Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed.
+ *
+ * @return the status
+ */
+ boolean getExecuteResult();
+
+ /**
+ * Gets the ActionInvocation associated with this ActionProxy.
+ *
+ * @return the ActionInvocation
+ */
+ ActionInvocation getInvocation();
+
+ /**
+ * Gets the namespace the ActionConfig for this ActionProxy is mapped to.
+ *
+ * @return the namespace
+ */
+ String getNamespace();
+
+ /**
+ * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
+ * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
+ *
+ * @return the result code returned from executing the ActionInvocation
+ * @throws Exception can be thrown.
+ * @see ActionInvocation
+ */
+ String execute() throws Exception;
+
+ /**
+ * Gets the method name to execute, or null if no method has been specified (meaning execute will be invoked).
+ *
+ * @return the method to execute
+ */
+ String getMethod();
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java
new file mode 100644
index 000000000..5cf4de016
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.util.Map;
+
+
+/**
+ * The {@link ActionProxyFactory} is used to create {@link ActionProxy}s to be executed.
+ *
+ * It is the entry point to XWork that is used by a dispatcher to create an {@link ActionProxy} to execute
+ * for a particular namespace and action name.
+ *
+ * @author Jason Carreira
+ * @see DefaultActionProxyFactory
+ */
+public interface ActionProxyFactory {
+
+ /**
+ * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+ * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+ *
+ * Note: This is the most used create method.
+ *
+ * @param namespace the namespace of the action, can be null
+ * @param actionName the name of the action
+ * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null
+ * @return ActionProxy the created action proxy
+ * @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map) instead}
+ */
+ @Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext);
+
+ /**
+ * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+ * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+ *
+ * Note: This is the most used create method.
+ *
+ * @param namespace the namespace of the action, can be null
+ * @param actionName the name of the action
+ * @param methodName the name of the method to execute
+ * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null
+ * @return ActionProxy the created action proxy
+ * @since 2.1.1
+ */
+ public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext);
+
+ /**
+ * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+ * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+ *
+ * @param namespace the namespace of the action, can be null
+ * @param actionName the name of the action
+ * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null
+ * @param executeResult flag which tells whether the result should be executed after the action
+ * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
+ * @return ActionProxy the created action proxy
+ * @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map,boolean,boolean)} instead
+ */
+ @Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext, boolean executeResult, boolean cleanupContext);
+
+ /**
+ * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+ * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+ *
+ * @param namespace the namespace of the action, can be null
+ * @param actionName the name of the action
+ * @param methodName the name of the method to execute
+ * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null
+ * @param executeResult flag which tells whether the result should be executed after the action
+ * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
+ * @return ActionProxy the created action proxy
+ * @since 2.1.1
+ */
+ public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext);
+
+
+ /**
+ * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+ * should be fully initialized when it is returned, including passed {@link ActionInvocation} instance.
+ *
+ * @param actionInvocation the action invocation instance to associate with
+ * @param namespace the namespace of the action, can be null
+ * @param actionName the name of the action
+ * @param methodName the name of the method to execute
+ * @param executeResult flag which tells whether the result should be executed after the action
+ * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
+ * @return ActionProxy the created action proxy
+ * @since 2.1.1
+ */
+ public ActionProxy createActionProxy(ActionInvocation actionInvocation, String namespace, String actionName, String methodName,
+ boolean executeResult, boolean cleanupContext);
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
new file mode 100644
index 000000000..530e0b43a
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+
+/**
+ * Provides a default implementation for the most common actions.
+ * See the documentation for all the interfaces this class implements for more detailed information.
+ */
+public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
+
+ protected static Logger LOG = LoggerFactory.getLogger(ActionSupport.class);
+
+ private final ValidationAwareSupport validationAware = new ValidationAwareSupport();
+
+ private transient TextProvider textProvider;
+ private Container container;
+
+ public void setActionErrors(Collection errorMessages) {
+ validationAware.setActionErrors(errorMessages);
+ }
+
+ public Collection getActionErrors() {
+ return validationAware.getActionErrors();
+ }
+
+ public void setActionMessages(Collection messages) {
+ validationAware.setActionMessages(messages);
+ }
+
+ public Collection getActionMessages() {
+ return validationAware.getActionMessages();
+ }
+
+ /**
+ * @deprecated Use {@link #getActionErrors()}.
+ */
+ @Deprecated
+ public Collection getErrorMessages() {
+ return getActionErrors();
+ }
+
+ /**
+ * @deprecated Use {@link #getFieldErrors()}.
+ */
+ @Deprecated
+ public Map> getErrors() {
+ return getFieldErrors();
+ }
+
+ public void setFieldErrors(Map> errorMap) {
+ validationAware.setFieldErrors(errorMap);
+ }
+
+ public Map> getFieldErrors() {
+ return validationAware.getFieldErrors();
+ }
+
+ public Locale getLocale() {
+ ActionContext ctx = ActionContext.getContext();
+ if (ctx != null) {
+ return ctx.getLocale();
+ } else {
+ LOG.debug("Action context not initialized");
+ return null;
+ }
+ }
+
+ public boolean hasKey(String key) {
+ return getTextProvider().hasKey(key);
+ }
+
+ public String getText(String aTextName) {
+ return getTextProvider().getText(aTextName);
+ }
+
+ public String getText(String aTextName, String defaultValue) {
+ return getTextProvider().getText(aTextName, defaultValue);
+ }
+
+ public String getText(String aTextName, String defaultValue, String obj) {
+ return getTextProvider().getText(aTextName, defaultValue, obj);
+ }
+
+ public String getText(String aTextName, List args) {
+ return getTextProvider().getText(aTextName, args);
+ }
+
+ public String getText(String key, String[] args) {
+ return getTextProvider().getText(key, args);
+ }
+
+ public String getText(String aTextName, String defaultValue, List args) {
+ return getTextProvider().getText(aTextName, defaultValue, args);
+ }
+
+ public String getText(String key, String defaultValue, String[] args) {
+ return getTextProvider().getText(key, defaultValue, args);
+ }
+
+ public String getText(String key, String defaultValue, List args, ValueStack stack) {
+ return getTextProvider().getText(key, defaultValue, args, stack);
+ }
+
+ public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+ return getTextProvider().getText(key, defaultValue, args, stack);
+ }
+
+ public ResourceBundle getTexts() {
+ return getTextProvider().getTexts();
+ }
+
+ public ResourceBundle getTexts(String aBundleName) {
+ return getTextProvider().getTexts(aBundleName);
+ }
+
+ public void addActionError(String anErrorMessage) {
+ validationAware.addActionError(anErrorMessage);
+ }
+
+ public void addActionMessage(String aMessage) {
+ validationAware.addActionMessage(aMessage);
+ }
+
+ public void addFieldError(String fieldName, String errorMessage) {
+ validationAware.addFieldError(fieldName, errorMessage);
+ }
+
+ public String input() throws Exception {
+ return INPUT;
+ }
+
+ public String doDefault() throws Exception {
+ return SUCCESS;
+ }
+
+ /**
+ * A default implementation that does nothing an returns "success".
+ *
+ * Subclasses should override this method to provide their business logic.
+ *
+ * See also {@link com.opensymphony.xwork2.Action#execute()}.
+ *
+ * @return returns {@link #SUCCESS}
+ * @throws Exception can be thrown by subclasses.
+ */
+ public String execute() throws Exception {
+ return SUCCESS;
+ }
+
+ public boolean hasActionErrors() {
+ return validationAware.hasActionErrors();
+ }
+
+ public boolean hasActionMessages() {
+ return validationAware.hasActionMessages();
+ }
+
+ public boolean hasErrors() {
+ return validationAware.hasErrors();
+ }
+
+ public boolean hasFieldErrors() {
+ return validationAware.hasFieldErrors();
+ }
+
+ /**
+ * Clears field errors. Useful for Continuations and other situations
+ * where you might want to clear parts of the state on the same action.
+ */
+ public void clearFieldErrors() {
+ validationAware.clearFieldErrors();
+ }
+
+ /**
+ * Clears action errors. Useful for Continuations and other situations
+ * where you might want to clear parts of the state on the same action.
+ */
+ public void clearActionErrors() {
+ validationAware.clearActionErrors();
+ }
+
+ /**
+ * Clears messages. Useful for Continuations and other situations
+ * where you might want to clear parts of the state on the same action.
+ */
+ public void clearMessages() {
+ validationAware.clearMessages();
+ }
+
+ /**
+ * Clears all errors. Useful for Continuations and other situations
+ * where you might want to clear parts of the state on the same action.
+ */
+ public void clearErrors() {
+ validationAware.clearErrors();
+ }
+
+ /**
+ * Clears all errors and messages. Useful for Continuations and other situations
+ * where you might want to clear parts of the state on the same action.
+ */
+ public void clearErrorsAndMessages() {
+ validationAware.clearErrorsAndMessages();
+ }
+
+ /**
+ * A default implementation that validates nothing.
+ * Subclasses should override this method to provide validations.
+ */
+ public void validate() {
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ /**
+ *
+ * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return
+ * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc.
+ *
+ *
+ * The next time this action is invoked (and using the same continuation ID), the method will resume immediately
+ * after where this method was called, with the entire call stack in the execute method restored.
+ *
+ *
+ * Note: this method can only be called within the {@link #execute()} method.
+ *
+ *
+ * @param result the result to return - the same type of return value in the {@link #execute()} method.
+ */
+ public void pause(String result) {
+ }
+
+ /**
+ * If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory},
+ * inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in,
+ * then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field
+ * for future references and at the returns reference to that field
+ *
+ * @return reference to field with TextProvider
+ */
+ private TextProvider getTextProvider() {
+ if (textProvider == null) {
+ TextProviderFactory tpf = new TextProviderFactory();
+ if (container != null) {
+ container.inject(tpf);
+ }
+ textProvider = tpf.createInstance(getClass(), this);
+ }
+ return textProvider;
+ }
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java
new file mode 100644
index 000000000..ec54b97c7
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java
@@ -0,0 +1,267 @@
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.util.*;
+
+
+/**
+ * This is a composite {@link TextProvider} that takes in an array or {@link java.util.List} of {@link TextProvider}s, it will
+ * consult each of them in order to get a composite result. To know how each method behaves, please refer to the
+ * javadoc for each methods.
+ *
+ * @author tmjee
+ * @version $Date$ $Id$
+ */
+public class CompositeTextProvider implements TextProvider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CompositeTextProvider.class);
+
+ private List textProviders = new ArrayList();
+
+ /**
+ * Instantiates a {@link CompositeTextProvider} with some predefined textProviders.
+ *
+ * @param textProviders
+ */
+ public CompositeTextProvider(List textProviders) {
+ this.textProviders.addAll(textProviders);
+ }
+
+ /**
+ * Instantiates a {@link CompositeTextProvider} with some predefined textProviders.
+ *
+ * @param textProviders
+ */
+ public CompositeTextProvider(TextProvider[] textProviders) {
+ this(Arrays.asList(textProviders));
+ }
+
+ /**
+ * @param key The key to lookup in ressource bundles.
+ * @return true , if the requested key is found in one of the ressource bundles.
+ * @see {@link com.opensymphony.xwork2.TextProvider#hasKey(String)}
+ * It will consult each individual {@link TextProvider}s and return true if either one of the
+ * {@link TextProvider} has such a key> else false.
+ */
+ public boolean hasKey(String key) {
+ // if there's a key in either text providers we are ok, else try the next text provider
+ for (TextProvider tp : textProviders) {
+ if (tp.hasKey(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key
+ *
+ * @param key The key to lookup in ressource bundles.
+ * @return The i18n text for the requested key.
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String)}
+ */
+ public String getText(String key) {
+ return getText(key, key, Collections.emptyList());
+ }
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key before returning defaultValue if every else fails.
+ *
+ * @param key
+ * @param defaultValue
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String)}
+ */
+ public String getText(String key, String defaultValue) {
+ return getText(key, defaultValue, Collections.emptyList());
+ }
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key, before returining defaultValue
+ * if every else fails.
+ *
+ * @param key
+ * @param defaultValue
+ * @param obj
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String)}
+ */
+ public String getText(String key, String defaultValue, final String obj) {
+ return getText(key, defaultValue, new ArrayList() {
+ {
+ add(obj);
+ }
+
+
+ });
+ }
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key.
+ *
+ * @param key
+ * @param args
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, java.util.List)}
+ */
+ public String getText(String key, List args) {
+ return getText(key, key, args);
+ }
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key.
+ *
+ * @param key
+ * @param args
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String[])}
+ */
+ public String getText(String key, String[] args) {
+ return getText(key, key, args);
+ }
+
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key, before returining defaultValue
+ *
+ * @param key
+ * @param defaultValue
+ * @param args
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText#getText(String, String, java.util.List)}
+ */
+ public String getText(String key, String defaultValue, List args) {
+ // if there's one text provider that gives us a msg not the same as defaultValue
+ // for this key, we are ok, else try the next
+ // text provider
+ for (TextProvider textProvider : textProviders) {
+ String msg = textProvider.getText(key, defaultValue, args);
+ if (msg != null && (!msg.equals(defaultValue))) {
+ return msg;
+ }
+ }
+ return defaultValue;
+ }
+
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key, before returining defaultValue.
+ *
+ * @param key
+ * @param defaultValue
+ * @param args
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[])}
+ */
+ public String getText(String key, String defaultValue, String[] args) {
+ // if there's one text provider that gives us a msg not the same as defaultValue
+ // for this key, we are ok, else try the next
+ // text provider
+ for (TextProvider textProvider : textProviders) {
+ String msg = textProvider.getText(key, defaultValue, args);
+ if (msg != null && (!msg.equals(defaultValue))) {
+ return msg;
+ }
+ }
+ return defaultValue;
+ }
+
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key, before returining defaultValue
+ *
+ * @param key
+ * @param defaultValue
+ * @param args
+ * @param stack
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, java.util.List, com.opensymphony.xwork2.util.OgnlValueStack)}
+ */
+ public String getText(String key, String defaultValue, List args, ValueStack stack) {
+ // if there's one text provider that gives us a msg not the same as defaultValue
+ // for this key, we are ok, else try the next
+ // text provider
+ for (TextProvider textProvider : textProviders) {
+ String msg = textProvider.getText(key, defaultValue, args, stack);
+ if (msg != null && (!msg.equals(defaultValue))) {
+ return msg;
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first valid message for this
+ * key, before returining defaultValue
+ *
+ * @param key
+ * @param defaultValue
+ * @param args
+ * @param stack
+ * @return
+ * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[], com.opensymphony.xwork2.util.ValueStack)}
+ */
+ public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+ // if there's one text provider that gives us a msg not the same as defaultValue
+ // for this key, we are ok, else try the next
+ // text provider
+ for (TextProvider textProvider : textProviders) {
+ String msg = textProvider.getText(key, defaultValue, args, stack);
+ if (msg != null && (!msg.equals(defaultValue))) {
+ return msg;
+ }
+ }
+ return defaultValue;
+ }
+
+
+ /**
+ * It will consult each {@link TextProvider}s and return the first non-null {@link ResourceBundle}.
+ *
+ * @param bundleName
+ * @return
+ * @see {@link TextProvider#getTexts(String)}
+ */
+ public ResourceBundle getTexts(String bundleName) {
+ // if there's one text provider that gives us a non-null resource bunlde for this bundleName, we are ok, else try the next
+ // text provider
+ for (TextProvider textProvider : textProviders) {
+ ResourceBundle bundle = textProvider.getTexts(bundleName);
+ if (bundle != null) {
+ return bundle;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * It will consult each {@link com.opensymphony.xwork2.TextProvider}s and return the first non-null {@link ResourceBundle}.
+ *
+ * @return
+ * @see {@link TextProvider#getTexts()}
+ */
+ public ResourceBundle getTexts() {
+ // if there's one text provider that gives us a non-null resource bundle, we are ok, else try the next
+ // text provider
+ for (TextProvider textProvider : textProviders) {
+ ResourceBundle bundle = textProvider.getTexts();
+ if (bundle != null) {
+ return bundle;
+ }
+ }
+ return null;
+ }
+}
+
+
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
new file mode 100644
index 000000000..0a6b237db
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.InterceptorMapping;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.PreResultListener;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * The Default ActionInvocation implementation
+ *
+ * @author Rainer Hermanns
+ * @author tmjee
+ * @version $Date$ $Id$
+ * @see com.opensymphony.xwork2.DefaultActionProxy
+ */
+public class DefaultActionInvocation implements ActionInvocation {
+
+ private static final long serialVersionUID = -585293628862447329L;
+
+ //static {
+ // if (ObjectFactory.getContinuationPackage() != null) {
+ // continuationHandler = new ContinuationHandler();
+ // }
+ //}
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultActionInvocation.class);
+
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ protected Object action;
+ protected ActionProxy proxy;
+ protected List preResultListeners;
+ protected Map extraContext;
+ protected ActionContext invocationContext;
+ protected Iterator interceptors;
+ protected ValueStack stack;
+ protected Result result;
+ protected Result explicitResult;
+ protected String resultCode;
+ protected boolean executed = false;
+ protected boolean pushAction = true;
+ protected ObjectFactory objectFactory;
+ protected ActionEventListener actionEventListener;
+ protected ValueStackFactory valueStackFactory;
+ protected Container container;
+ private Configuration configuration;
+ protected UnknownHandlerManager unknownHandlerManager;
+
+ public DefaultActionInvocation(final Map extraContext, final boolean pushAction) {
+ DefaultActionInvocation.this.extraContext = extraContext;
+ DefaultActionInvocation.this.pushAction = pushAction;
+ }
+
+ @Inject
+ public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) {
+ this.unknownHandlerManager = unknownHandlerManager;
+ }
+
+ @Inject
+ public void setValueStackFactory(ValueStackFactory fac) {
+ this.valueStackFactory = fac;
+ }
+
+ @Inject
+ public void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Inject
+ public void setObjectFactory(ObjectFactory fac) {
+ this.objectFactory = fac;
+ }
+
+ @Inject
+ public void setContainer(Container cont) {
+ this.container = cont;
+ }
+
+ @Inject(required=false)
+ public void setActionEventListener(ActionEventListener listener) {
+ this.actionEventListener = listener;
+ }
+
+ public Object getAction() {
+ return action;
+ }
+
+ public boolean isExecuted() {
+ return executed;
+ }
+
+ public ActionContext getInvocationContext() {
+ return invocationContext;
+ }
+
+ public ActionProxy getProxy() {
+ return proxy;
+ }
+
+ /**
+ * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method
+ * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
+ * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with
+ * the result params.
+ *
+ * @return a Result instance
+ * @throws Exception
+ */
+ public Result getResult() throws Exception {
+ Result returnResult = result;
+
+ // If we've chained to other Actions, we need to find the last result
+ while (returnResult instanceof ActionChainResult) {
+ ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy();
+
+ if (aProxy != null) {
+ Result proxyResult = aProxy.getInvocation().getResult();
+
+ if ((proxyResult != null) && (aProxy.getExecuteResult())) {
+ returnResult = proxyResult;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return returnResult;
+ }
+
+ public String getResultCode() {
+ return resultCode;
+ }
+
+ public void setResultCode(String resultCode) {
+ if (isExecuted())
+ throw new IllegalStateException("Result has already been executed.");
+
+ this.resultCode = resultCode;
+ }
+
+
+ public ValueStack getStack() {
+ return stack;
+ }
+
+ /**
+ * Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the
+ * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order
+ * in which they are registered. Listener registration and execution does not need to be thread-safe.
+ *
+ * @param listener
+ */
+ public void addPreResultListener(PreResultListener listener) {
+ if (preResultListeners == null) {
+ preResultListeners = new ArrayList(1);
+ }
+
+ preResultListeners.add(listener);
+ }
+
+ public Result createResult() throws Exception {
+
+ if (explicitResult != null) {
+ Result ret = explicitResult;
+ explicitResult = null;
+
+ return ret;
+ }
+ ActionConfig config = proxy.getConfig();
+ Map results = config.getResults();
+
+ ResultConfig resultConfig = null;
+
+ try {
+ resultConfig = results.get(resultCode);
+ } catch (NullPointerException e) {
+ // swallow
+ }
+
+ if (resultConfig == null) {
+ // If no result is found for the given resultCode, try to get a wildcard '*' match.
+ resultConfig = results.get("*");
+ }
+
+ if (resultConfig != null) {
+ try {
+ return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
+ } catch (Exception e) {
+ LOG.error("There was an exception while instantiating the result of type " + resultConfig.getClassName(), e);
+ throw new XWorkException(e, resultConfig);
+ }
+ } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
+ return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
+ }
+ return null;
+ }
+
+ /**
+ * @throws ConfigurationException If no result can be found with the returned code
+ */
+ public String invoke() throws Exception {
+ String profileKey = "invoke: ";
+ try {
+ UtilTimerStack.push(profileKey);
+
+ if (executed) {
+ throw new IllegalStateException("Action has already executed");
+ }
+
+ if (interceptors.hasNext()) {
+ final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
+ String interceptorMsg = "interceptor: " + interceptor.getName();
+ UtilTimerStack.push(interceptorMsg);
+ try {
+ resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
+ }
+ finally {
+ UtilTimerStack.pop(interceptorMsg);
+ }
+ } else {
+ resultCode = invokeActionOnly();
+ }
+
+ // this is needed because the result will be executed, then control will return to the Interceptor, which will
+ // return above and flow through again
+ if (!executed) {
+ if (preResultListeners != null) {
+ for (Object preResultListener : preResultListeners) {
+ PreResultListener listener = (PreResultListener) preResultListener;
+
+ String _profileKey = "preResultListener: ";
+ try {
+ UtilTimerStack.push(_profileKey);
+ listener.beforeResult(this, resultCode);
+ }
+ finally {
+ UtilTimerStack.pop(_profileKey);
+ }
+ }
+ }
+
+ // now execute the result, if we're supposed to
+ if (proxy.getExecuteResult()) {
+ executeResult();
+ }
+
+ executed = true;
+ }
+
+ return resultCode;
+ }
+ finally {
+ UtilTimerStack.pop(profileKey);
+ }
+ }
+
+ public String invokeActionOnly() throws Exception {
+ return invokeAction(getAction(), proxy.getConfig());
+ }
+
+ protected void createAction(Map contextMap) {
+ // load action
+ String timerKey = "actionCreate: " + proxy.getActionName();
+ try {
+ UtilTimerStack.push(timerKey);
+ action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
+ } catch (InstantiationException e) {
+ throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
+ } catch (IllegalAccessException e) {
+ throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
+ } catch (Exception e) {
+ String gripe = "";
+
+ if (proxy == null) {
+ gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad";
+ } else if (proxy.getConfig() == null) {
+ gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?";
+ } else if (proxy.getConfig().getClassName() == null) {
+ gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
+ } else {
+ gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
+ }
+
+ gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
+ throw new XWorkException(gripe, e, proxy.getConfig());
+ } finally {
+ UtilTimerStack.pop(timerKey);
+ }
+
+ if (actionEventListener != null) {
+ action = actionEventListener.prepare(action, stack);
+ }
+ }
+
+ protected Map createContextMap() {
+ Map contextMap;
+
+ if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) {
+ // In case the ValueStack was passed in
+ stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK);
+
+ if (stack == null) {
+ throw new IllegalStateException("There was a null Stack set into the extra params.");
+ }
+
+ contextMap = stack.getContext();
+ } else {
+ // create the value stack
+ // this also adds the ValueStack to its context
+ stack = valueStackFactory.createValueStack();
+
+ // create the action context
+ contextMap = stack.getContext();
+ }
+
+ // put extraContext in
+ if (extraContext != null) {
+ contextMap.putAll(extraContext);
+ }
+
+ //put this DefaultActionInvocation into the context map
+ contextMap.put(ActionContext.ACTION_INVOCATION, this);
+ contextMap.put(ActionContext.CONTAINER, container);
+
+ return contextMap;
+ }
+
+ /**
+ * Uses getResult to get the final Result and executes it
+ *
+ * @throws ConfigurationException If not result can be found with the returned code
+ */
+ private void executeResult() throws Exception {
+ result = createResult();
+
+ String timerKey = "executeResult: " + getResultCode();
+ try {
+ UtilTimerStack.push(timerKey);
+ if (result != null) {
+ result.execute(this);
+ } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
+ throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
+ + " and result " + getResultCode(), proxy.getConfig());
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
+ }
+ }
+ } finally {
+ UtilTimerStack.pop(timerKey);
+ }
+ }
+
+ public void init(ActionProxy proxy) {
+ this.proxy = proxy;
+ Map contextMap = createContextMap();
+
+ // Setting this so that other classes, like object factories, can use the ActionProxy and other
+ // contextual information to operate
+ ActionContext actionContext = ActionContext.getContext();
+
+ if (actionContext != null) {
+ actionContext.setActionInvocation(this);
+ }
+
+ createAction(contextMap);
+
+ if (pushAction) {
+ stack.push(action);
+ contextMap.put("action", action);
+ }
+
+ invocationContext = new ActionContext(contextMap);
+ invocationContext.setName(proxy.getActionName());
+
+ // get a new List so we don't get problems with the iterator if someone changes the list
+ List interceptorList = new ArrayList(proxy.getConfig().getInterceptors());
+ interceptors = interceptorList.iterator();
+ }
+
+ protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
+ String methodName = proxy.getMethod();
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Executing action method = " + actionConfig.getMethodName());
+ }
+
+ String timerKey = "invokeAction: " + proxy.getActionName();
+ try {
+ UtilTimerStack.push(timerKey);
+
+ boolean methodCalled = false;
+ Object methodResult = null;
+ Method method = null;
+ try {
+ method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
+ } catch (NoSuchMethodException e) {
+ // hmm -- OK, try doXxx instead
+ try {
+ String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
+ method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY);
+ } catch (NoSuchMethodException e1) {
+ // well, give the unknown handler a shot
+ if (unknownHandlerManager.hasUnknownHandlers()) {
+ try {
+ methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
+ methodCalled = true;
+ } catch (NoSuchMethodException e2) {
+ // throw the original one
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ if (!methodCalled) {
+ methodResult = method.invoke(action, new Object[0]);
+ }
+
+ if (methodResult instanceof Result) {
+ this.explicitResult = (Result) methodResult;
+
+ // Wire the result automatically
+ container.inject(explicitResult);
+ return null;
+ } else {
+ return (String) methodResult;
+ }
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
+ } catch (InvocationTargetException e) {
+ // We try to return the source exception.
+ Throwable t = e.getTargetException();
+
+ if (actionEventListener != null) {
+ String result = actionEventListener.handleException(t, getStack());
+ if (result != null) {
+ return result;
+ }
+ }
+ if (t instanceof Exception) {
+ throw (Exception) t;
+ } else {
+ throw e;
+ }
+ } finally {
+ UtilTimerStack.pop(timerKey);
+ }
+ }
+
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
new file mode 100644
index 000000000..ef0d3b4d0
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.LocalizedTextUtil;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+import org.apache.commons.lang.StringUtils;
+
+
+/**
+ * The Default ActionProxy implementation
+ *
+ * @author Rainer Hermanns
+ * @author Revised by Henry Hu
+ * @author tmjee
+ *
+ * @version $Date$ $Id$
+ * @since 2005-8-6
+ */
+public class DefaultActionProxy implements ActionProxy, Serializable {
+
+ private static final long serialVersionUID = 3293074152487468527L;
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultActionProxy.class);
+
+ protected Configuration configuration;
+ protected ActionConfig config;
+ protected ActionInvocation invocation;
+ protected UnknownHandlerManager unknownHandlerManager;
+ protected String actionName;
+ protected String namespace;
+ protected String method;
+ protected boolean executeResult;
+ protected boolean cleanupContext;
+
+ protected ObjectFactory objectFactory;
+
+ protected ActionEventListener actionEventListener;
+
+ /**
+ * This constructor is private so the builder methods (create*) should be used to create an DefaultActionProxy.
+ *
+ * The reason for the builder methods is so that you can use a subclass to create your own DefaultActionProxy instance
+ * (like a RMIActionProxy).
+ */
+ protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
+
+ this.invocation = inv;
+ this.cleanupContext = cleanupContext;
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Creating an DefaultActionProxy for namespace " + namespace + " and action name " + actionName);
+ }
+
+ this.actionName = actionName;
+ this.namespace = namespace;
+ this.executeResult = executeResult;
+ this.method = methodName;
+ }
+
+ @Inject
+ public void setObjectFactory(ObjectFactory factory) {
+ this.objectFactory = factory;
+ }
+
+ @Inject
+ public void setConfiguration(Configuration config) {
+ this.configuration = config;
+ }
+
+ @Inject
+ public void setUnknownHandler(UnknownHandlerManager unknownHandlerManager) {
+ this.unknownHandlerManager = unknownHandlerManager;
+ }
+
+ @Inject(required=false)
+ public void setActionEventListener(ActionEventListener listener) {
+ this.actionEventListener = listener;
+ }
+
+ public Object getAction() {
+ return invocation.getAction();
+ }
+
+ public String getActionName() {
+ return actionName;
+ }
+
+ public ActionConfig getConfig() {
+ return config;
+ }
+
+ public void setExecuteResult(boolean executeResult) {
+ this.executeResult = executeResult;
+ }
+
+ public boolean getExecuteResult() {
+ return executeResult;
+ }
+
+ public ActionInvocation getInvocation() {
+ return invocation;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String execute() throws Exception {
+ ActionContext nestedContext = ActionContext.getContext();
+ ActionContext.setContext(invocation.getInvocationContext());
+
+ String retCode = null;
+
+ String profileKey = "execute: ";
+ try {
+ UtilTimerStack.push(profileKey);
+
+ retCode = invocation.invoke();
+ } finally {
+ if (cleanupContext) {
+ ActionContext.setContext(nestedContext);
+ }
+ UtilTimerStack.pop(profileKey);
+ }
+
+ return retCode;
+ }
+
+
+ public String getMethod() {
+ return method;
+ }
+
+ private void resolveMethod() {
+ // if the method is set to null, use the one from the configuration
+ // if the one from the configuration is also null, use "execute"
+ if (StringUtils.isEmpty(this.method)) {
+ this.method = config.getMethodName();
+ if (StringUtils.isEmpty(this.method)) {
+ this.method = "execute";
+ }
+ }
+ }
+
+ protected void prepare() {
+ String profileKey = "create DefaultActionProxy: ";
+ try {
+ UtilTimerStack.push(profileKey);
+ config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
+
+ if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
+ config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
+ }
+ if (config == null) {
+ String message;
+
+ if ((namespace != null) && (namespace.trim().length() > 0)) {
+ message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
+ namespace, actionName
+ });
+ } else {
+ message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
+ actionName
+ });
+ }
+ throw new ConfigurationException(message);
+ }
+
+ resolveMethod();
+
+ if (!config.isAllowedMethod(method)) {
+ throw new ConfigurationException("Invalid method: "+method+" for action "+actionName);
+ }
+
+ invocation.init(this);
+
+ } finally {
+ UtilTimerStack.pop(profileKey);
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxyFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxyFactory.java
new file mode 100644
index 000000000..ba0e8fa3f
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxyFactory.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+
+
+/**
+ * Default factory for {@link com.opensymphony.xwork2.ActionProxyFactory}.
+ *
+ * @author Jason Carreira
+ */
+public class DefaultActionProxyFactory implements ActionProxyFactory {
+
+ protected Container container;
+
+ public DefaultActionProxyFactory() {
+ super();
+ }
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+ public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext) {
+ return createActionProxy(namespace, actionName, null, extraContext, true, true);
+ }
+
+ public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext) {
+ return createActionProxy(namespace, actionName, methodName, extraContext, true, true);
+ }
+
+ public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext, boolean executeResult, boolean cleanupContext) {
+ return createActionProxy(namespace, actionName, null, extraContext, executeResult, cleanupContext);
+ }
+
+ public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext) {
+
+ ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
+ container.inject(inv);
+ return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
+ }
+
+ public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, boolean executeResult, boolean cleanupContext) {
+
+ return createActionProxy(inv, namespace, actionName, null, executeResult, cleanupContext);
+ }
+
+ public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
+
+ DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
+ container.inject(proxy);
+ proxy.prepare();
+ return proxy;
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
new file mode 100644
index 000000000..02d72ec46
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.LocalizedTextUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ResourceBundle;
+
+/**
+ * DefaultTextProvider gets texts from only the default resource bundles associated with the
+ * LocalizedTextUtil.
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * @see LocalizedTextUtil#addDefaultResourceBundle(String)
+ */
+public class DefaultTextProvider implements TextProvider, Serializable, Unchainable {
+
+ private static final Object[] EMPTY_ARGS = new Object[0];
+
+ public DefaultTextProvider() {
+ }
+
+ public boolean hasKey(String key) {
+ return getText(key) != null;
+ }
+
+ public String getText(String key) {
+ return LocalizedTextUtil.findDefaultText(key, ActionContext.getContext().getLocale());
+ }
+
+ public String getText(String key, String defaultValue) {
+ String text = getText(key);
+ if (text == null) {
+ return defaultValue;
+ }
+ return text;
+ }
+
+ public String getText(String key, List args) {
+ Object[] params;
+ if (args != null) {
+ params = args.toArray();
+ } else {
+ params = EMPTY_ARGS;
+ }
+
+ return LocalizedTextUtil.findDefaultText(key, ActionContext.getContext().getLocale(), params);
+ }
+
+ public String getText(String key, String[] args) {
+ Object[] params;
+ if (args != null) {
+ params = args;
+ } else {
+ params = EMPTY_ARGS;
+ }
+
+ return LocalizedTextUtil.findDefaultText(key, ActionContext.getContext().getLocale(), params);
+ }
+
+ public String getText(String key, String defaultValue, List args) {
+ String text = getText(key, args);
+ if(text == null && defaultValue == null) {
+ defaultValue = key;
+ }
+ if (text == null && defaultValue != null) {
+
+ MessageFormat format = new MessageFormat(defaultValue);
+ format.setLocale(ActionContext.getContext().getLocale());
+ format.applyPattern(defaultValue);
+
+ Object[] params;
+ if (args != null) {
+ params = args.toArray();
+ } else {
+ params = EMPTY_ARGS;
+ }
+
+ return format.format(params);
+ }
+ return text;
+ }
+
+ public String getText(String key, String defaultValue, String[] args) {
+ String text = getText(key, args);
+ if (text == null) {
+ MessageFormat format = new MessageFormat(defaultValue);
+ format.setLocale(ActionContext.getContext().getLocale());
+ format.applyPattern(defaultValue);
+
+ if (args == null) {
+ return format.format(EMPTY_ARGS);
+ }
+
+ return format.format(args);
+ }
+ return text;
+ }
+
+
+ public String getText(String key, String defaultValue, String obj) {
+ List args = new ArrayList(1);
+ args.add(obj);
+ return getText(key, defaultValue, args);
+ }
+
+ public String getText(String key, String defaultValue, List args, ValueStack stack) {
+ //we're not using the value stack here
+ return getText(key, defaultValue, args);
+ }
+
+ public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+ //we're not using the value stack here
+ List values = new ArrayList(Arrays.asList(args));
+ return getText(key, defaultValue, values);
+ }
+
+ public ResourceBundle getTexts(String bundleName) {
+ return LocalizedTextUtil.findResourceBundle(bundleName, ActionContext.getContext().getLocale());
+ }
+
+ public ResourceBundle getTexts() {
+ return null;
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultUnknownHandlerManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultUnknownHandlerManager.java
new file mode 100644
index 000000000..057a0019f
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultUnknownHandlerManager.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.UnknownHandler;
+import com.opensymphony.xwork2.UnknownHandlerManager;
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+/**
+ * Default implementation of UnknownHandlerManager
+ *
+ * @see com.opensymphony.xwork2.UnknownHandlerManager
+ */
+public class DefaultUnknownHandlerManager implements UnknownHandlerManager {
+ protected ArrayList unknownHandlers;
+ private Configuration configuration;
+ private Container container;
+
+ @Inject
+ public void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ build();
+ }
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ build();
+ }
+
+ /**
+ * Builds a list of UnknowHandlers in the order specified by the configured "unknown-handler-stack".
+ * If "unknown-handler-stack" was not configured, all UnknowHandlers will be returned, in no specific order
+ */
+ protected void build() {
+ if (configuration != null && container != null) {
+ List unkownHandlerStack = configuration.getUnknownHandlerStack();
+ unknownHandlers = new ArrayList();
+
+ if (unkownHandlerStack != null && !unkownHandlerStack.isEmpty()) {
+ //get UnknownHandlers in the specified order
+ for (UnknownHandlerConfig unknownHandlerConfig : unkownHandlerStack) {
+ UnknownHandler uh = container.getInstance(UnknownHandler.class, unknownHandlerConfig.getName());
+ unknownHandlers.add(uh);
+ }
+ } else {
+ //add all available UnknownHandlers
+ Set unknowHandlerNames = container.getInstanceNames(UnknownHandler.class);
+ if (unknowHandlerNames != null) {
+ for (String unknowHandlerName : unknowHandlerNames) {
+ UnknownHandler uh = container.getInstance(UnknownHandler.class, unknowHandlerName);
+ unknownHandlers.add(uh);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Iterate over UnknownHandlers and return the result of the first one that can handle it
+ */
+ public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) {
+ for (UnknownHandler unknownHandler : unknownHandlers) {
+ Result result = unknownHandler.handleUnknownResult(actionContext, actionName, actionConfig, resultCode);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Iterate over UnknownHandlers and return the result of the first one that can handle it
+ *
+ * @throws NoSuchMethodException
+ */
+ public Object handleUnknownMethod(Object action, String methodName) throws NoSuchMethodException {
+ for (UnknownHandler unknownHandler : unknownHandlers) {
+ Object result = unknownHandler.handleUnknownActionMethod(action, methodName);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Iterate over UnknownHandlers and return the result of the first one that can handle it
+ *
+ * @throws NoSuchMethodException
+ */
+ public ActionConfig handleUnknownAction(String namespace, String actionName) {
+ for (UnknownHandler unknownHandler : unknownHandlers) {
+ ActionConfig result = unknownHandler.handleUnknownAction(namespace, actionName);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ public boolean hasUnknownHandlers() {
+ return unknownHandlers != null && !unknownHandlers.isEmpty();
+ }
+
+ public List getUnknownHandlers() {
+ return unknownHandlers;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/InvalidMetadataException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/InvalidMetadataException.java
new file mode 100644
index 000000000..eb556f5a4
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/InvalidMetadataException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+/**
+ * InvalidMetadataException
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+public class InvalidMetadataException extends RuntimeException {
+
+ /**
+ * Create a new InvalidMetadataException with the supplied error message.
+ *
+ * @param msg the error message
+ */
+ public InvalidMetadataException(String msg) {
+ super(msg);
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java
new file mode 100644
index 000000000..6fc4ea981
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.util.Locale;
+
+
+/**
+ * Indicates that the implementing class can provide its own {@link Locale}.
+ *
+ * This is useful for when an action may wish override the default locale. All that is
+ * needed is to implement this interface and return your own custom locale.
+ * The {@link TextProvider} interface uses this interface heavily for retrieving
+ * internationalized messages from resource bundles.
+ *
+ * @author Jason Carreira
+ */
+public interface LocaleProvider {
+
+ /**
+ * Gets the provided locale.
+ *
+ * @return the locale.
+ */
+ Locale getLocale();
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/MockActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/MockActionInvocation.java
new file mode 100644
index 000000000..3eea89bce
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/MockActionInvocation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opensymphony.xwork2;
+
+/**
+ * Mock for an {@link ActionInvocation}.
+ *
+ * @author plightbo
+ * @deprecated Please use @see com.opensymphony.xwork2.mock.MockActionInvocation instead
+ */
+@Deprecated public class MockActionInvocation extends com.opensymphony.xwork2.mock.MockActionInvocation {
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ModelDriven.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ModelDriven.java
new file mode 100644
index 000000000..2f5f6c7ef
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ModelDriven.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+
+/**
+ * ModelDriven Actions provide a model object to be pushed onto the ValueStack
+ * in addition to the Action itself, allowing a FormBean type approach like Struts.
+ *
+ * @author Jason Carreira
+ */
+public interface ModelDriven {
+
+ /**
+ * Gets the model to be pushed onto the ValueStack instead of the Action itself.
+ *
+ * @return the model
+ */
+ T getModel();
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java
new file mode 100644
index 000000000..34a0159dc
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.InterceptorConfig;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionException;
+import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import com.opensymphony.xwork2.validator.Validator;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * ObjectFactory is responsible for building the core framework objects. Users may register their
+ * own implementation of the ObjectFactory to control instantiation of these Objects.
+ *
+ * This default implementation uses the {@link #buildBean(Class,java.util.Map) buildBean}
+ * method to create all classes (interceptors, actions, results, etc).
+ *
+ *
+ * @author Jason Carreira
+ */
+public class ObjectFactory implements Serializable {
+ private static final Logger LOG = LoggerFactory.getLogger(ObjectFactory.class);
+
+ private transient ClassLoader ccl;
+ private Container container;
+ protected ReflectionProvider reflectionProvider;
+
+ @Inject(value="objectFactory.classloader", required=false)
+ public void setClassLoader(ClassLoader cl) {
+ this.ccl = cl;
+ }
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider prov) {
+ this.reflectionProvider = prov;
+ }
+
+ public ObjectFactory() {
+ }
+
+ public ObjectFactory(ReflectionProvider prov) {
+ this.reflectionProvider = prov;
+ }
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+ /**
+ * @deprecated Since 2.1
+ */
+ @Deprecated public static ObjectFactory getObjectFactory() {
+ return ActionContext.getContext().getContainer().getInstance(ObjectFactory.class);
+ }
+
+ /**
+ * Allows for ObjectFactory implementations that support
+ * Actions without no-arg constructors.
+ *
+ * @return true if no-arg constructor is required, false otherwise
+ */
+ public boolean isNoArgConstructorRequired() {
+ return true;
+ }
+
+ /**
+ * Utility method to obtain the class matched to className. Caches look ups so that subsequent
+ * lookups will be faster.
+ *
+ * @param className The fully qualified name of the class to return
+ * @return The class itself
+ * @throws ClassNotFoundException
+ */
+ public Class getClassInstance(String className) throws ClassNotFoundException {
+ if (ccl != null) {
+ return ccl.loadClass(className);
+ }
+
+ return ClassLoaderUtil.loadClass(className, this.getClass());
+ }
+
+ /**
+ * Build an instance of the action class to handle a particular request (eg. web request)
+ * @param actionName the name the action configuration is set up with in the configuration
+ * @param namespace the namespace the action is configured in
+ * @param config the action configuration found in the config for the actionName / namespace
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ * @return instance of the action class to handle a web request
+ * @throws Exception
+ */
+ public Object buildAction(String actionName, String namespace, ActionConfig config, Map extraContext) throws Exception {
+ return buildBean(config.getClassName(), extraContext);
+ }
+
+ /**
+ * Build a generic Java object of the given type.
+ *
+ * @param clazz the type of Object to build
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ */
+ public Object buildBean(Class clazz, Map extraContext) throws Exception {
+ return clazz.newInstance();
+ }
+
+ /**
+ * @param obj
+ */
+ protected Object injectInternalBeans(Object obj) {
+ if (obj != null && container != null) {
+ container.inject(obj);
+ }
+ return obj;
+ }
+
+ /**
+ * Build a generic Java object of the given type.
+ *
+ * @param className the type of Object to build
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ */
+ public Object buildBean(String className, Map extraContext) throws Exception {
+ return buildBean(className, extraContext, true);
+ }
+
+ /**
+ * Build a generic Java object of the given type.
+ *
+ * @param className the type of Object to build
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ */
+ public Object buildBean(String className, Map extraContext, boolean injectInternal) throws Exception {
+ Class clazz = getClassInstance(className);
+ Object obj = buildBean(clazz, extraContext);
+ if (injectInternal) {
+ injectInternalBeans(obj);
+ }
+ return obj;
+ }
+
+ /**
+ * Builds an Interceptor from the InterceptorConfig and the Map of
+ * parameters from the interceptor reference. Implementations of this method
+ * should ensure that the Interceptor is parameterized with both the
+ * parameters from the Interceptor config and the interceptor ref Map (the
+ * interceptor ref params take precedence), and that the Interceptor.init()
+ * method is called on the Interceptor instance before it is returned.
+ *
+ * @param interceptorConfig the InterceptorConfig from the configuration
+ * @param interceptorRefParams a Map of params provided in the Interceptor reference in the
+ * Action mapping or InterceptorStack definition
+ */
+ public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map interceptorRefParams) throws ConfigurationException {
+ String interceptorClassName = interceptorConfig.getClassName();
+ Map thisInterceptorClassParams = interceptorConfig.getParams();
+ Map params = (thisInterceptorClassParams == null) ? new HashMap() : new HashMap(thisInterceptorClassParams);
+ params.putAll(interceptorRefParams);
+
+ String message;
+ Throwable cause;
+
+ try {
+ // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context
+ Interceptor interceptor = (Interceptor) buildBean(interceptorClassName, null);
+ reflectionProvider.setProperties(params, interceptor);
+ interceptor.init();
+
+ return interceptor;
+ } catch (InstantiationException e) {
+ cause = e;
+ message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
+ } catch (IllegalAccessException e) {
+ cause = e;
+ message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
+ } catch (ClassCastException e) {
+ cause = e;
+ message = "Class [" + interceptorClassName + "] does not implement com.opensymphony.xwork2.interceptor.Interceptor";
+ } catch (Exception e) {
+ cause = e;
+ message = "Caught Exception while registering Interceptor class " + interceptorClassName;
+ } catch (NoClassDefFoundError e) {
+ cause = e;
+ message = "Could not load class " + interceptorClassName + ". Perhaps it exists but certain dependencies are not available?";
+ }
+
+ throw new ConfigurationException(message, cause, interceptorConfig);
+ }
+
+ /**
+ * Build a Result using the type in the ResultConfig and set the parameters in the ResultConfig.
+ *
+ * @param resultConfig the ResultConfig found for the action with the result code returned
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ */
+ public Result buildResult(ResultConfig resultConfig, Map extraContext) throws Exception {
+ String resultClassName = resultConfig.getClassName();
+ Result result = null;
+
+ if (resultClassName != null) {
+ result = (Result) buildBean(resultClassName, extraContext);
+ Map params = resultConfig.getParams();
+ if (params != null) {
+ for (Map.Entry paramEntry : params.entrySet()) {
+ try {
+ reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), result, extraContext, true);
+ } catch (ReflectionException ex) {
+ if (result instanceof ReflectionExceptionHandler) {
+ ((ReflectionExceptionHandler) result).handle(ex);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Build a Validator of the given type and set the parameters on it
+ *
+ * @param className the type of Validator to build
+ * @param params property name -> value Map to set onto the Validator instance
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ */
+ public Validator buildValidator(String className, Map params, Map extraContext) throws Exception {
+ Validator validator = (Validator) buildBean(className, null);
+ reflectionProvider.setProperties(params, validator);
+
+ return validator;
+ }
+
+ static class ContinuationsClassLoader extends ClassLoader {
+
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Preparable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Preparable.java
new file mode 100644
index 000000000..58a2412cd
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Preparable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+
+/**
+ * Preparable Actions will have their prepare() method called if the {@link com.opensymphony.xwork2.interceptor.PrepareInterceptor}
+ * is applied to the ActionConfig.
+ *
+ * @author Jason Carreira
+ * @see com.opensymphony.xwork2.interceptor.PrepareInterceptor
+ */
+public interface Preparable {
+
+ /**
+ * This method is called to allow the action to prepare itself.
+ *
+ * @throws Exception thrown if a system level exception occurs.
+ */
+ void prepare() throws Exception;
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ResourceBundleTextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ResourceBundleTextProvider.java
new file mode 100644
index 000000000..1415e716e
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ResourceBundleTextProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.util.ResourceBundle;
+
+/**
+ * Extension Interface for TextProvider to help supporting ResourceBundles.
+ *
+ * @author Rene Gielen
+ */
+public interface ResourceBundleTextProvider extends TextProvider {
+
+ /**
+ * Set the resource bundle to use.
+ *
+ * @param bundle the bundle to use.
+ */
+ void setBundle(ResourceBundle bundle);
+
+ /**
+ * Set the class to use for reading the resource bundle.
+ *
+ * @param clazz the class to use for loading.
+ */
+ void setClazz(Class clazz);
+
+ /**
+ * Set the LocaleProvider to use.
+ *
+ * @param localeProvider the LocaleProvider to use.
+ */
+ void setLocaleProvider(LocaleProvider localeProvider);
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Result.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Result.java
new file mode 100644
index 000000000..3d6537766
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Result.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.io.Serializable;
+
+
+/**
+ * All results (except for Action.NONE) of an {@link Action} are mapped to a View implementation.
+ *
+ * Examples of Views might be:
+ *
+ * SwingPanelView - pops up a new Swing panel
+ * ActionChainView - executes another action
+ * SerlvetRedirectView - redirects the HTTP response to a URL
+ * ServletDispatcherView - dispatches the HTTP response to a URL
+ *
+ *
+ * @author plightbo
+ */
+public interface Result extends Serializable {
+
+ /**
+ * Represents a generic interface for all action execution results.
+ * Whether that be displaying a webpage, generating an email, sending a JMS message, etc.
+ *
+ * @param invocation the invocation context.
+ * @throws Exception can be thrown.
+ */
+ public void execute(ActionInvocation invocation) throws Exception;
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TestNGXWorkTestCase.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TestNGXWorkTestCase.java
new file mode 100644
index 000000000..823e764be
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TestNGXWorkTestCase.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationManager;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.config.impl.MockConfiguration;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.XWorkTestCaseHelper;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+
+/**
+ * Base test class for TestNG unit tests. Provides common XWork variables
+ * and performs XWork setup and teardown processes
+ */
+public class TestNGXWorkTestCase {
+
+ protected ConfigurationManager configurationManager;
+ protected Configuration configuration;
+ protected Container container;
+ protected ActionProxyFactory actionProxyFactory;
+
+ @BeforeTest
+ protected void setUp() throws Exception {
+ configurationManager = XWorkTestCaseHelper.setUp();
+ configuration = new MockConfiguration();
+ ((MockConfiguration) configuration).selfRegister();
+ container = configuration.getContainer();
+ actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+ }
+
+ @AfterTest
+ protected void tearDown() throws Exception {
+ XWorkTestCaseHelper.tearDown(configurationManager);
+ configurationManager = null;
+ configuration = null;
+ container = null;
+ actionProxyFactory = null;
+ }
+
+ protected void loadConfigurationProviders(ConfigurationProvider... providers) {
+ configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
+ configuration = configurationManager.getConfiguration();
+ container = configuration.getContainer();
+ actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProvider.java
new file mode 100644
index 000000000..742eb4609
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProvider.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.util.List;
+import java.util.ResourceBundle;
+
+
+/**
+ * Provides access to {@link ResourceBundle}s and their underlying text messages.
+ * Implementing classes can delegate {@link TextProviderSupport}. Messages will be
+ * searched in multiple resource bundles, startinag with the one associated with
+ * this particular class (action in most cases), continuing to try the message
+ * bundle associated with each superclass as well. It will stop once a bundle is
+ * found that contains the given text. This gives a cascading style that allow
+ * global texts to be defined for an application base class.
+ *
+ * You can override {@link LocaleProvider#getLocale()} to change the behaviour of how
+ * to choose locale for the bundles that are returned. Typically you would
+ * use the {@link LocaleProvider} interface to get the users configured locale.
+ *
+ * When you want to use your own implementation for Struts 2 project you have to define following
+ * bean and constant in struts.xml:
+ * <bean class="org.demo.MyTextProvider" name="myTextProvider" type="com.opensymphony.xwork2.TextProvider" />
+ * <constant name="struts.xworkTextProvider" value="myTextProvider" />
+ *
+ * if you want to also use your implemntation for framework's messages define another constant (remember to put
+ * into it all framework messages)
+ * <constant name="system" value="myTextProvider" />
+ *
+ * Take a look on {@link com.opensymphony.xwork2.ActionSupport} for example TextProvider implemntation.
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * @see LocaleProvider
+ * @see TextProviderSupport
+ */
+public interface TextProvider {
+
+ /**
+ * Checks if a message key exists.
+ *
+ * @param key message key to check for
+ * @return boolean true if key exists, false otherwise.
+ */
+ boolean hasKey(String key);
+
+ /**
+ * Gets a message based on a message key, or null if no message is found.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @return the message as found in the resource bundle, or null if none is found.
+ */
+ String getText(String key);
+
+ /**
+ * Gets a message based on a key, or, if the message is not found, a supplied
+ * default value is returned.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ String getText(String key, String defaultValue);
+
+ /**
+ * Gets a message based on a key using the supplied obj, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param obj obj to be used in a {@link java.text.MessageFormat} message
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ String getText(String key, String defaultValue, String obj);
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or null if no message is found.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param args a list args to be used in a {@link java.text.MessageFormat} message
+ * @return the message as found in the resource bundle, or null if none is found.
+ */
+ String getText(String key, List args);
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or null if no message is found.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param args an array args to be used in a {@link java.text.MessageFormat} message
+ * @return the message as found in the resource bundle, or null if none is found.
+ */
+ String getText(String key, String[] args);
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param args a list args to be used in a {@link java.text.MessageFormat} message
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ String getText(String key, String defaultValue, List args);
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param args an array args to be used in a {@link java.text.MessageFormat} message
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ String getText(String key, String defaultValue, String[] args);
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned. Instead of using the value stack in the ActionContext
+ * this version of the getText() method uses the provided value stack.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param args a list args to be used in a {@link java.text.MessageFormat} message
+ * @param stack the value stack to use for finding the text
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ String getText(String key, String defaultValue, List args, ValueStack stack);
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned. Instead of using the value stack in the ActionContext
+ * this version of the getText() method uses the provided value stack.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param args an array args to be used in a {@link java.text.MessageFormat} message
+ * @param stack the value stack to use for finding the text
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ String getText(String key, String defaultValue, String[] args, ValueStack stack);
+
+ /**
+ * Get the named bundle, such as "com/acme/Foo".
+ *
+ * @param bundleName the name of the resource bundle, such as "com/acme/Foo".
+ * @return the bundle
+ */
+ ResourceBundle getTexts(String bundleName);
+
+ /**
+ * Get the resource bundle associated with the implementing class (usually an action).
+ *
+ * @return the bundle
+ */
+ ResourceBundle getTexts();
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderFactory.java
new file mode 100644
index 000000000..bcb6d932b
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.ResourceBundle;
+
+/**
+ * This factory enables users to provide and correctly initialize a custom TextProvider.
+ *
+ * @author Oleg Gorobets
+ * @author Rene Gielen
+ */
+public class TextProviderFactory {
+
+ private TextProvider textProvider;
+
+ @Inject
+ public void setTextProvider(TextProvider textProvider) {
+ this.textProvider = textProvider;
+ }
+
+ protected TextProvider getTextProvider() {
+ if (this.textProvider == null) {
+ return new TextProviderSupport();
+ } else {
+ return textProvider;
+ }
+ }
+
+ public TextProvider createInstance(Class clazz, LocaleProvider provider) {
+ TextProvider instance = getTextProvider();
+ if (instance instanceof ResourceBundleTextProvider) {
+ ((ResourceBundleTextProvider) instance).setClazz(clazz);
+ ((ResourceBundleTextProvider) instance).setLocaleProvider(provider);
+ }
+ return instance;
+ }
+
+ public TextProvider createInstance(ResourceBundle bundle, LocaleProvider provider) {
+ TextProvider instance = getTextProvider();
+ if (instance instanceof ResourceBundleTextProvider) {
+ ((ResourceBundleTextProvider) instance).setBundle(bundle);
+ ((ResourceBundleTextProvider) instance).setLocaleProvider(provider);
+ }
+ return instance;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderSupport.java
new file mode 100644
index 000000000..188976610
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderSupport.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.LocalizedTextUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.util.*;
+
+
+/**
+ * Default TextProvider implementation.
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ */
+public class TextProviderSupport implements ResourceBundleTextProvider {
+
+ private Class clazz;
+ private LocaleProvider localeProvider;
+ private ResourceBundle bundle;
+
+ /**
+ * Default constructor
+ */
+ public TextProviderSupport() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param clazz a clazz to use for reading the resource bundle.
+ * @param provider a locale provider.
+ */
+ public TextProviderSupport(Class clazz, LocaleProvider provider) {
+ this.clazz = clazz;
+ this.localeProvider = provider;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param bundle the resource bundle.
+ * @param provider a locale provider.
+ */
+ public TextProviderSupport(ResourceBundle bundle, LocaleProvider provider) {
+ this.bundle = bundle;
+ this.localeProvider = provider;
+ }
+
+ /**
+ * @param bundle the resource bundle.
+ */
+ public void setBundle(ResourceBundle bundle) {
+ this.bundle = bundle;
+ }
+
+ /**
+ * @param clazz a clazz to use for reading the resource bundle.
+ */
+ public void setClazz(Class clazz) {
+ this.clazz = clazz;
+ }
+
+
+ /**
+ * @param localeProvider a locale provider.
+ */
+ public void setLocaleProvider(LocaleProvider localeProvider) {
+ this.localeProvider = localeProvider;
+ }
+
+
+ /**
+ * Checks if a key is available in the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class.
+ */
+ public boolean hasKey(String key) {
+ String message;
+ if (clazz != null) {
+ message = LocalizedTextUtil.findText(clazz, key, getLocale(), null, new Object[0] );
+ } else {
+ message = LocalizedTextUtil.findText(bundle, key, getLocale(), null, new Object[0]);
+ }
+ return message != null;
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class.
+ *
+ * @param key name of text to be found
+ * @return value of named text
+ */
+ public String getText(String key) {
+ return getText(key, key, Collections.emptyList());
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class. If no text is found for this text name, the default value is returned.
+ *
+ * @param key name of text to be found
+ * @param defaultValue the default value which will be returned if no text is found
+ * @return value of named text
+ */
+ public String getText(String key, String defaultValue) {
+ return getText(key, defaultValue, Collections.emptyList());
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class. If no text is found for this text name, the default value is returned.
+ *
+ * @param key name of text to be found
+ * @param defaultValue the default value which will be returned if no text is found
+ * @return value of named text
+ */
+ public String getText(String key, String defaultValue, String arg) {
+ List args = new ArrayList();
+ args.add(arg);
+ return getText(key, defaultValue, args);
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class. If no text is found for this text name, the default value is returned.
+ *
+ * @param key name of text to be found
+ * @param args a List of args to be used in a MessageFormat message
+ * @return value of named text
+ */
+ public String getText(String key, List args) {
+ return getText(key, key, args);
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class. If no text is found for this text name, the default value is returned.
+ *
+ * @param key name of text to be found
+ * @param args an array of args to be used in a MessageFormat message
+ * @return value of named text
+ */
+ public String getText(String key, String[] args) {
+ return getText(key, key, args);
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class. If no text is found for this text name, the default value is returned.
+ *
+ * @param key name of text to be found
+ * @param defaultValue the default value which will be returned if no text is found
+ * @param args a List of args to be used in a MessageFormat message
+ * @return value of named text
+ */
+ public String getText(String key, String defaultValue, List args) {
+ Object[] argsArray = ((args != null && !args.equals(Collections.emptyList())) ? args.toArray() : null);
+ if (clazz != null) {
+ return LocalizedTextUtil.findText(clazz, key, getLocale(), defaultValue, argsArray);
+ } else {
+ return LocalizedTextUtil.findText(bundle, key, getLocale(), defaultValue, argsArray);
+ }
+ }
+
+ /**
+ * Get a text from the resource bundles associated with this action.
+ * The resource bundles are searched, starting with the one associated
+ * with this particular action, and testing all its superclasses' bundles.
+ * It will stop once a bundle is found that contains the given text. This gives
+ * a cascading style that allow global texts to be defined for an application base
+ * class. If no text is found for this text name, the default value is returned.
+ *
+ * @param key name of text to be found
+ * @param defaultValue the default value which will be returned if no text is found
+ * @param args an array of args to be used in a MessageFormat message
+ * @return value of named text
+ */
+ public String getText(String key, String defaultValue, String[] args) {
+ if (clazz != null) {
+ return LocalizedTextUtil.findText(clazz, key, getLocale(), defaultValue, args);
+ } else {
+ return LocalizedTextUtil.findText(bundle, key, getLocale(), defaultValue, args);
+ }
+ }
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned. Instead of using the value stack in the ActionContext
+ * this version of the getText() method uses the provided value stack.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param args a list args to be used in a {@link java.text.MessageFormat} message
+ * @param stack the value stack to use for finding the text
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ public String getText(String key, String defaultValue, List args, ValueStack stack) {
+ Object[] argsArray = ((args != null) ? args.toArray() : null);
+ Locale locale;
+ if (stack == null){
+ locale = getLocale();
+ }else{
+ locale = (Locale) stack.getContext().get(ActionContext.LOCALE);
+ }
+ if (locale == null) {
+ locale = getLocale();
+ }
+ if (clazz != null) {
+ return LocalizedTextUtil.findText(clazz, key, locale, defaultValue, argsArray, stack);
+ } else {
+ return LocalizedTextUtil.findText(bundle, key, locale, defaultValue, argsArray, stack);
+ }
+ }
+
+
+ /**
+ * Gets a message based on a key using the supplied args, as defined in
+ * {@link java.text.MessageFormat}, or, if the message is not found, a supplied
+ * default value is returned. Instead of using the value stack in the ActionContext
+ * this version of the getText() method uses the provided value stack.
+ *
+ * @param key the resource bundle key that is to be searched for
+ * @param defaultValue the default value which will be returned if no message is found
+ * @param args an array args to be used in a {@link java.text.MessageFormat} message
+ * @param stack the value stack to use for finding the text
+ * @return the message as found in the resource bundle, or defaultValue if none is found
+ */
+ public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+ Locale locale;
+ if (stack == null){
+ locale = getLocale();
+ }else{
+ locale = (Locale) stack.getContext().get(ActionContext.LOCALE);
+ }
+ if (locale == null) {
+ locale = getLocale();
+ }
+ if (clazz != null) {
+ return LocalizedTextUtil.findText(clazz, key, locale, defaultValue, args, stack);
+ } else {
+ return LocalizedTextUtil.findText(bundle, key, locale, defaultValue, args, stack);
+ }
+
+ }
+
+ /**
+ * Get the named bundle.
+ *
+ * You can override the getLocale() methodName to change the behaviour of how
+ * to choose locale for the bundles that are returned. Typically you would
+ * use the TextProvider interface to get the users configured locale, or use
+ * your own methodName to allow the user to select the locale and store it in
+ * the session (by using the SessionAware interface).
+ *
+ * @param aBundleName bundle name
+ * @return a resource bundle
+ */
+ public ResourceBundle getTexts(String aBundleName) {
+ return LocalizedTextUtil.findResourceBundle(aBundleName, getLocale());
+ }
+
+ /**
+ * Get the resource bundle associated with this action.
+ * This will be based on the actual subclass that is used.
+ *
+ * @return resouce bundle
+ */
+ public ResourceBundle getTexts() {
+ if (clazz != null) {
+ return getTexts(clazz.getName());
+ }
+ return bundle;
+ }
+
+ /**
+ * Get's the locale from the localeProvider.
+ *
+ * @return the locale from the localeProvider.
+ */
+ private Locale getLocale() {
+ return localeProvider.getLocale();
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Unchainable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Unchainable.java
new file mode 100644
index 000000000..19d88ef4f
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Unchainable.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opensymphony.xwork2;
+
+/**
+ * Simple marker interface to indicate an object should not have its properties copied during chaining.
+ *
+ * @see com.opensymphony.xwork2.interceptor.ChainingInterceptor
+ */
+public interface Unchainable {
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java
new file mode 100644
index 000000000..033c0d6f5
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+/**
+ * Handles cases when the result or action is unknown.
+ *
+ * This allows other classes like Struts plugins to provide intelligent defaults easier.
+ */
+public interface UnknownHandler {
+
+ /**
+ * Handles the case when an action configuration is unknown. Implementations can return a new ActionConfig
+ * to be used to process the request.
+ *
+ * @param namespace The namespace
+ * @param actionName The action name
+ * @return An generated ActionConfig, can return null
+ * @throws XWorkException
+ */
+ public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException;
+
+ /**
+ * Handles the case when a result cannot be found for an action and result code.
+ *
+ * @param actionContext The action context
+ * @param actionName The action name
+ * @param actionConfig The action config
+ * @param resultCode The returned result code
+ * @return A result to be executed, can return null
+ * @throws XWorkException
+ */
+ public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) throws XWorkException;
+
+ /**
+ * Handles the case when an action method cannot be found. This method is responsible both for finding the method and executing it.
+ *
+ * @since 2.1
+ * @param action The action object
+ * @param methodName The method name to call
+ * @return The result returned from invoking the action method
+ * @throws NoSuchMethodException If the method cannot be found
+ */
+ public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException;
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandlerManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandlerManager.java
new file mode 100644
index 000000000..325c0ffe8
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandlerManager.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+import java.util.List;
+
+/**
+ * An unknown handler manager contains a list of UnknownHandler and iterates on them by order
+ *
+ * @see com.opensymphony.xwork2.DefaultUnknownHandlerManager
+ */
+public interface UnknownHandlerManager {
+ Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode);
+
+ Object handleUnknownMethod(Object action, String methodName) throws NoSuchMethodException;
+
+ ActionConfig handleUnknownAction(String namespace, String actionName);
+
+ boolean hasUnknownHandlers();
+
+ List getUnknownHandlers();
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Validateable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Validateable.java
new file mode 100644
index 000000000..889a28440
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Validateable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+
+/**
+ * Provides an interface in which a call for a validation check can be done.
+ *
+ * @author Jason Carreira
+ * @see ActionSupport
+ * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor
+ */
+public interface Validateable {
+
+ /**
+ * Performs validation.
+ */
+ void validate();
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAware.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAware.java
new file mode 100644
index 000000000..4ae5e842e
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAware.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept
+ * in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs.
+ *
+ * @author plightbo
+ */
+public interface ValidationAware {
+
+ /**
+ * Set the Collection of Action-level String error messages.
+ *
+ * @param errorMessages Collection of String error messages
+ */
+ void setActionErrors(Collection errorMessages);
+
+ /**
+ * Get the Collection of Action-level error messages for this action. Error messages should not
+ * be added directly here, as implementations are free to return a new Collection or an
+ * Unmodifiable Collection.
+ *
+ * @return Collection of String error messages
+ */
+ Collection getActionErrors();
+
+ /**
+ * Set the Collection of Action-level String messages (not errors).
+ *
+ * @param messages Collection of String messages (not errors).
+ */
+ void setActionMessages(Collection messages);
+
+ /**
+ * Get the Collection of Action-level messages for this action. Messages should not be added
+ * directly here, as implementations are free to return a new Collection or an Unmodifiable
+ * Collection.
+ *
+ * @return Collection of String messages
+ */
+ Collection getActionMessages();
+
+ /**
+ * Set the field error map of fieldname (String) to Collection of String error messages.
+ *
+ * @param errorMap field error map
+ */
+ void setFieldErrors(Map> errorMap);
+
+ /**
+ * Get the field specific errors associated with this action. Error messages should not be added
+ * directly here, as implementations are free to return a new Collection or an Unmodifiable
+ * Collection.
+ *
+ * @return Map with errors mapped from fieldname (String) to Collection of String error messages
+ */
+ Map> getFieldErrors();
+
+ /**
+ * Add an Action-level error message to this Action.
+ *
+ * @param anErrorMessage the error message
+ */
+ void addActionError(String anErrorMessage);
+
+ /**
+ * Add an Action-level message to this Action.
+ *
+ * @param aMessage the message
+ */
+ void addActionMessage(String aMessage);
+
+ /**
+ * Add an error message for a given field.
+ *
+ * @param fieldName name of field
+ * @param errorMessage the error message
+ */
+ void addFieldError(String fieldName, String errorMessage);
+
+ /**
+ * Check whether there are any Action-level error messages.
+ *
+ * @return true if any Action-level error messages have been registered
+ */
+ boolean hasActionErrors();
+
+ /**
+ * Checks whether there are any Action-level messages.
+ *
+ * @return true if any Action-level messages have been registered
+ */
+ boolean hasActionMessages();
+
+ /**
+ * Checks whether there are any action errors or field errors.
+ *
+ * Note : that this does not have the same meaning as in WW 1.x.
+ *
+ * @return (hasActionErrors() || hasFieldErrors())
+ */
+ boolean hasErrors();
+
+ /**
+ * Check whether there are any field errors associated with this action.
+ *
+ * @return whether there are any field errors
+ */
+ boolean hasFieldErrors();
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAwareSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAwareSupport.java
new file mode 100644
index 000000000..27eb93d70
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAwareSupport.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * Provides a default implementation of ValidationAware. Returns new collections for
+ * errors and messages (defensive copy).
+ *
+ * @author Jason Carreira
+ * @author tm_jee
+ * @version $Date$ $Id$
+ */
+public class ValidationAwareSupport implements ValidationAware, Serializable {
+
+ private Collection actionErrors;
+ private Collection actionMessages;
+ private Map> fieldErrors;
+
+
+ public synchronized void setActionErrors(Collection errorMessages) {
+ this.actionErrors = errorMessages;
+ }
+
+ public synchronized Collection getActionErrors() {
+ return new ArrayList(internalGetActionErrors());
+ }
+
+ public synchronized void setActionMessages(Collection messages) {
+ this.actionMessages = messages;
+ }
+
+ public synchronized Collection getActionMessages() {
+ return new ArrayList(internalGetActionMessages());
+ }
+
+ public synchronized void setFieldErrors(Map> errorMap) {
+ this.fieldErrors = errorMap;
+ }
+
+ public synchronized Map> getFieldErrors() {
+ return new LinkedHashMap>(internalGetFieldErrors());
+ }
+
+ public synchronized void addActionError(String anErrorMessage) {
+ internalGetActionErrors().add(anErrorMessage);
+ }
+
+ public synchronized void addActionMessage(String aMessage) {
+ internalGetActionMessages().add(aMessage);
+ }
+
+ public synchronized void addFieldError(String fieldName, String errorMessage) {
+ final Map> errors = internalGetFieldErrors();
+ List thisFieldErrors = errors.get(fieldName);
+
+ if (thisFieldErrors == null) {
+ thisFieldErrors = new ArrayList();
+ errors.put(fieldName, thisFieldErrors);
+ }
+
+ thisFieldErrors.add(errorMessage);
+ }
+
+ public synchronized boolean hasActionErrors() {
+ return (actionErrors != null) && !actionErrors.isEmpty();
+ }
+
+ public synchronized boolean hasActionMessages() {
+ return (actionMessages != null) && !actionMessages.isEmpty();
+ }
+
+ public synchronized boolean hasErrors() {
+ return (hasActionErrors() || hasFieldErrors());
+ }
+
+ public synchronized boolean hasFieldErrors() {
+ return (fieldErrors != null) && !fieldErrors.isEmpty();
+ }
+
+ private Collection internalGetActionErrors() {
+ if (actionErrors == null) {
+ actionErrors = new ArrayList();
+ }
+
+ return actionErrors;
+ }
+
+ private Collection internalGetActionMessages() {
+ if (actionMessages == null) {
+ actionMessages = new ArrayList();
+ }
+
+ return actionMessages;
+ }
+
+ private Map> internalGetFieldErrors() {
+ if (fieldErrors == null) {
+ fieldErrors = new LinkedHashMap>();
+ }
+
+ return fieldErrors;
+ }
+
+ /**
+ * Clears field errors map.
+ *
+ * Will clear the map that contains field errors.
+ */
+ public synchronized void clearFieldErrors() {
+ internalGetFieldErrors().clear();
+ }
+
+ /**
+ * Clears action errors list.
+ *
+ * Will clear the list that contains action errors.
+ */
+ public synchronized void clearActionErrors() {
+ internalGetActionErrors().clear();
+ }
+
+ /**
+ * Clears messages list.
+ *
+ * Will clear the list that contains action messages.
+ */
+ public synchronized void clearMessages() {
+ internalGetActionMessages().clear();
+ }
+
+ /**
+ * Clears all error list/maps.
+ *
+ * Will clear the map and list that contain
+ * field errors and action errors.
+ */
+ public synchronized void clearErrors() {
+ internalGetFieldErrors().clear();
+ internalGetActionErrors().clear();
+ }
+
+ /**
+ * Clears all error and messages list/maps.
+ *
+ * Will clear the maps/lists that contain
+ * field errors, action errors and action messages.
+ */
+ public synchronized void clearErrorsAndMessages() {
+ internalGetFieldErrors().clear();
+ internalGetActionErrors().clear();
+ internalGetActionMessages().clear();
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWork.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWork.java
new file mode 100644
index 000000000..fddc75bc6
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWork.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationManager;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Simple facade to make using XWork standalone easier
+ */
+public class XWork {
+
+ ConfigurationManager configurationManager;
+
+ public XWork() {
+ this(new ConfigurationManager());
+ }
+
+ public XWork(ConfigurationManager mgr) {
+ this.configurationManager = mgr;
+ }
+
+ public void setLoggerFactory(LoggerFactory factory) {
+ LoggerFactory.setLoggerFactory(factory);
+ }
+
+ /**
+ * Executes an action
+ *
+ * @param namespace The namespace
+ * @param name The action name
+ * @param method The method name
+ * @throws Exception If anything goes wrong
+ */
+ public void executeAction(String namespace, String name, String method) throws XWorkException {
+ Map extraContext = Collections.emptyMap();
+ executeAction(namespace, name, method, extraContext);
+ }
+
+ /**
+ * Executes an action with extra context information
+ *
+ * @param namespace The namespace
+ * @param name The action name
+ * @param method The method name
+ * @param extraContext A map of extra context information
+ * @throws Exception If anything goes wrong
+ */
+ public void executeAction(String namespace, String name, String method, Map extraContext) throws XWorkException {
+ Configuration config = configurationManager.getConfiguration();
+ try {
+ ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
+ namespace, name, method, extraContext, true, false);
+
+ proxy.execute();
+ } catch (Exception e) {
+ throw new XWorkException(e);
+ } finally {
+ ActionContext.setContext(null);
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkException.java
new file mode 100644
index 000000000..3242ce9e6
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkException.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.location.Locatable;
+import com.opensymphony.xwork2.util.location.Location;
+import com.opensymphony.xwork2.util.location.LocationUtils;
+
+
+/**
+ * A generic runtime exception that optionally contains Location information
+ *
+ * @author Jason Carreira
+ */
+public class XWorkException extends RuntimeException implements Locatable {
+
+ private Location location;
+
+
+ /**
+ * Constructs a XWorkException with no detail message.
+ */
+ public XWorkException() {
+ }
+
+ /**
+ * Constructs a XWorkException with the specified
+ * detail message.
+ *
+ * @param s the detail message.
+ */
+ public XWorkException(String s) {
+ this(s, null, null);
+ }
+
+ /**
+ * Constructs a XWorkException with the specified
+ * detail message and target.
+ *
+ * @param s the detail message.
+ * @param target the target of the exception.
+ */
+ public XWorkException(String s, Object target) {
+ this(s, (Throwable) null, target);
+ }
+
+ /**
+ * Constructs a XWorkException with the root cause
+ *
+ * @param cause The wrapped exception
+ */
+ public XWorkException(Throwable cause) {
+ this(null, cause, null);
+ }
+
+ /**
+ * Constructs a XWorkException with the root cause and target
+ *
+ * @param cause The wrapped exception
+ * @param target The target of the exception
+ */
+ public XWorkException(Throwable cause, Object target) {
+ this(null, cause, target);
+ }
+
+ /**
+ * Constructs a XWorkException with the specified
+ * detail message and exception cause.
+ *
+ * @param s the detail message.
+ * @param cause the wrapped exception
+ */
+ public XWorkException(String s, Throwable cause) {
+ this(s, cause, null);
+ }
+
+
+ /**
+ * Constructs a XWorkException with the specified
+ * detail message, cause, and target
+ *
+ * @param s the detail message.
+ * @param cause The wrapped exception
+ * @param target The target of the exception
+ */
+ public XWorkException(String s, Throwable cause, Object target) {
+ super(s, cause);
+
+ this.location = LocationUtils.getLocation(target);
+ if (this.location == Location.UNKNOWN) {
+ this.location = LocationUtils.getLocation(cause);
+ }
+ }
+
+
+ /**
+ * Gets the underlying cause
+ *
+ * @return the underlying cause, null if no cause
+ * @deprecated Use {@link #getCause()}
+ */
+ @Deprecated public Throwable getThrowable() {
+ return getCause();
+ }
+
+
+ /**
+ * Gets the location of the error, if available
+ *
+ * @return the location, null if not available
+ */
+ public Location getLocation() {
+ return this.location;
+ }
+
+
+ /**
+ * Returns a short description of this throwable object, including the
+ * location. If no detailed message is available, it will use the message
+ * of the underlying exception if available.
+ *
+ * @return a string representation of this Throwable.
+ */
+ @Override
+ public String toString() {
+ String msg = getMessage();
+ if (msg == null && getCause() != null) {
+ msg = getCause().getMessage();
+ }
+
+ if (location != null) {
+ if (msg != null) {
+ return msg + " - " + location.toString();
+ } else {
+ return location.toString();
+ }
+ } else {
+ return msg;
+ }
+ }
+}
\ No newline at end of file
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkMessages.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkMessages.java
new file mode 100644
index 000000000..d187acc51
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkMessages.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2;
+
+
+/**
+ * Contains constants for some default XWork messages.
+ *
+ * @author Jason Carreira
+ */
+public interface XWorkMessages {
+
+ public static final String ACTION_EXECUTION_ERROR = "xwork.error.action.execution";
+ public static final String MISSING_ACTION_EXCEPTION = "xwork.exception.missing-action";
+ public static final String MISSING_PACKAGE_ACTION_EXCEPTION = "xwork.exception.missing-package-action";
+ public static final String DEFAULT_INVALID_FIELDVALUE = "xwork.default.invalid.fieldvalue";
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
new file mode 100644
index 000000000..c9b4e32f9
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationManager;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.inject.*;
+import com.opensymphony.xwork2.test.StubConfigurationProvider;
+import com.opensymphony.xwork2.util.XWorkTestCaseHelper;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+import junit.framework.TestCase;
+
+
+/**
+ * Base JUnit TestCase to extend for XWork specific JUnit tests. Uses
+ * the generic test setup for logic.
+ *
+ * @author plightbo
+ */
+public abstract class XWorkTestCase extends TestCase {
+
+ protected ConfigurationManager configurationManager;
+ protected Configuration configuration;
+ protected Container container;
+ protected ActionProxyFactory actionProxyFactory;
+
+ public XWorkTestCase() {
+ super();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ configurationManager = XWorkTestCaseHelper.setUp();
+ configuration = configurationManager.getConfiguration();
+ container = configuration.getContainer();
+ actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ XWorkTestCaseHelper.tearDown(configurationManager);
+ configurationManager = null;
+ configuration = null;
+ container = null;
+ actionProxyFactory = null;
+ }
+
+ protected void loadConfigurationProviders(ConfigurationProvider... providers) {
+ configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
+ configuration = configurationManager.getConfiguration();
+ container = configuration.getContainer();
+ actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+ }
+
+ protected void loadButAdd(final Class> type, final Object impl) {
+ loadButAdd(type, Container.DEFAULT_NAME, impl);
+ }
+
+ protected void loadButAdd(final Class> type, final String name, final Object impl) {
+ loadConfigurationProviders(new StubConfigurationProvider() {
+ @Override
+ public void register(ContainerBuilder builder,
+ LocatableProperties props) throws ConfigurationException {
+ builder.factory(type, name, new Factory() {
+ public Object create(Context context) throws Exception {
+ return impl;
+ }
+
+ }, Scope.SINGLETON);
+ }
+ });
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/Configuration.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/Configuration.java
new file mode 100644
index 000000000..be1359d5e
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/Configuration.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.config.entities.PackageConfig;
+import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
+import com.opensymphony.xwork2.inject.Container;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * XWork configuration.
+ *
+ * @author Mike
+ */
+public interface Configuration extends Serializable {
+
+ void rebuildRuntimeConfiguration();
+
+ PackageConfig getPackageConfig(String name);
+
+ Set getPackageConfigNames();
+
+ Map getPackageConfigs();
+
+ /**
+ * The current runtime configuration. Currently, if changes have been made to the Configuration since the last
+ * time buildRuntimeConfiguration() was called, you'll need to make sure to.
+ *
+ * @return the current runtime configuration
+ */
+ RuntimeConfiguration getRuntimeConfiguration();
+
+ void addPackageConfig(String name, PackageConfig packageConfig);
+
+ /**
+ * Removes a package from the the list of packages. Changes to the configuration won't take effect until buildRuntimeConfiguration
+ * is called.
+ * @param packageName the name of the package to remove
+ * @return the package removed (if any)
+ */
+ PackageConfig removePackageConfig(String packageName);
+
+ /**
+ * Allow the Configuration to clean up any resources that have been used.
+ */
+ void destroy();
+
+ /**
+ * @deprecated Since 2.1
+ * @param providers
+ * @throws ConfigurationException
+ */
+ @Deprecated void reload(List providers) throws ConfigurationException;
+
+ /**
+ * @since 2.1
+ * @param containerProviders
+ * @throws ConfigurationException
+ */
+ List reloadContainer(List containerProviders) throws ConfigurationException;
+
+ /**
+ * @return the container
+ */
+ Container getContainer();
+
+ Set getLoadedFileNames();
+
+ /**
+ * @since 2.1
+ * @return list of unknown handlers
+ */
+ List getUnknownHandlerStack();
+
+ /**
+ * @since 2.1
+ * @param unknownHandlerStack
+ */
+ void setUnknownHandlerStack(List unknownHandlerStack);
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationException.java
new file mode 100644
index 000000000..fcbb11bc8
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.XWorkException;
+
+
+/**
+ * ConfigurationException
+ *
+ * @author Jason Carreira
+ */
+public class ConfigurationException extends XWorkException {
+
+ /**
+ * Constructs a ConfigurationException with no detail message.
+ */
+ public ConfigurationException() {
+ }
+
+ /**
+ * Constructs a ConfigurationException with the specified
+ * detail message.
+ *
+ * @param s the detail message.
+ */
+ public ConfigurationException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs a ConfigurationException with the specified
+ * detail message.
+ *
+ * @param s the detail message.
+ */
+ public ConfigurationException(String s, Object target) {
+ super(s, target);
+ }
+
+ /**
+ * Constructs a ConfigurationException with no detail message.
+ */
+ public ConfigurationException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a ConfigurationException with no detail message.
+ */
+ public ConfigurationException(Throwable cause, Object target) {
+ super(cause, target);
+ }
+
+ /**
+ * Constructs a ConfigurationException with the specified
+ * detail message.
+ *
+ * @param s the detail message.
+ */
+ public ConfigurationException(String s, Throwable cause) {
+ super(s, cause);
+ }
+
+ /**
+ * Constructs a ConfigurationException with the specified
+ * detail message.
+ *
+ * @param s the detail message.
+ */
+ public ConfigurationException(String s, Throwable cause, Object target) {
+ super(s, cause, target);
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationManager.java
new file mode 100644
index 000000000..84d89e940
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationManager.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.config.impl.DefaultConfiguration;
+import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider;
+import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import com.opensymphony.xwork2.util.FileManager;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+/**
+ * ConfigurationManager - central for XWork Configuration management, including
+ * its ConfigurationProvider.
+ *
+ * @author Jason Carreira
+ * @author tm_jee
+ * @version $Date$ $Id$
+ */
+public class ConfigurationManager {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(ConfigurationManager.class);
+ protected Configuration configuration;
+ protected Lock providerLock = new ReentrantLock();
+ private List containerProviders = new CopyOnWriteArrayList();
+ private List packageProviders = new CopyOnWriteArrayList();
+ protected String defaultFrameworkBeanName;
+
+ public ConfigurationManager() {
+ this("xwork");
+ }
+
+ public ConfigurationManager(String name) {
+ this.defaultFrameworkBeanName = name;
+ }
+
+ /**
+ * Get the current XWork configuration object. By default an instance of DefaultConfiguration will be returned
+ *
+ * @see com.opensymphony.xwork2.config.impl.DefaultConfiguration
+ */
+ public synchronized Configuration getConfiguration() {
+ if (configuration == null) {
+ setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));
+ try {
+ configuration.reloadContainer(getContainerProviders());
+ } catch (ConfigurationException e) {
+ setConfiguration(null);
+ throw new ConfigurationException("Unable to load configuration.", e);
+ }
+ } else {
+ conditionalReload();
+ }
+
+ return configuration;
+ }
+
+ public synchronized void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ /**
+ * Get the current list of ConfigurationProviders. If no custom ConfigurationProviders have been added, this method
+ * will return a list containing only the default ConfigurationProvider, XMLConfigurationProvider. if a custom
+ * ConfigurationProvider has been added, then the XmlConfigurationProvider must be added by hand.
+ *
+ *
+ * WARNING: This returns only ContainerProviders that can be cast into ConfigurationProviders
+ *
+ * @return the list of registered ConfigurationProvider objects
+ * @see ConfigurationProvider
+ * @deprecated Since 2.1, use {@link #getContainerProviders()}
+ */
+ @Deprecated public List getConfigurationProviders() {
+ List contProviders = getContainerProviders();
+ List providers = new ArrayList();
+ for (ContainerProvider prov : contProviders) {
+ if (prov instanceof ConfigurationProvider) {
+ providers.add((ConfigurationProvider) prov);
+ }
+ }
+ return providers;
+ }
+
+ /**
+ * Get the current list of ConfigurationProviders. If no custom ConfigurationProviders have been added, this method
+ * will return a list containing only the default ConfigurationProvider, XMLConfigurationProvider. if a custom
+ * ConfigurationProvider has been added, then the XmlConfigurationProvider must be added by hand.
+ *
+ *
+ * TODO: the lazy instantiation of XmlConfigurationProvider should be refactored to be elsewhere. the behavior described above seems unintuitive.
+ *
+ * @return the list of registered ConfigurationProvider objects
+ * @see ConfigurationProvider
+ */
+ public List getContainerProviders() {
+ providerLock.lock();
+ try {
+ if (containerProviders.size() == 0) {
+ containerProviders.add(new XWorkConfigurationProvider());
+ containerProviders.add(new XmlConfigurationProvider("xwork.xml", false));
+ }
+
+ return containerProviders;
+ } finally {
+ providerLock.unlock();
+ }
+ }
+
+ /**
+ * Set the list of configuration providers
+ *
+ * @param configurationProviders
+ * @deprecated Since 2.1, use {@link #setContainerProvider()}
+ */
+ @Deprecated public void setConfigurationProviders(List configurationProviders) {
+ // Silly copy necessary due to lack of ability to cast generic lists
+ List contProviders = new ArrayList();
+ contProviders.addAll(configurationProviders);
+
+ setContainerProviders(contProviders);
+ }
+
+ /**
+ * Set the list of configuration providers
+ *
+ * @param containerProviders
+ */
+ public void setContainerProviders(List containerProviders) {
+ providerLock.lock();
+ try {
+ this.containerProviders = new CopyOnWriteArrayList(containerProviders);
+ } finally {
+ providerLock.unlock();
+ }
+ }
+
+ /**
+ * adds a configuration provider to the List of ConfigurationProviders. a given ConfigurationProvider may be added
+ * more than once
+ *
+ * @param provider the ConfigurationProvider to register
+ * @deprecated Since 2.1, use {@link #addContainerProvider()}
+ */
+ @Deprecated public void addConfigurationProvider(ConfigurationProvider provider) {
+ addContainerProvider(provider);
+ }
+
+ /**
+ * adds a configuration provider to the List of ConfigurationProviders. a given ConfigurationProvider may be added
+ * more than once
+ *
+ * @param provider the ConfigurationProvider to register
+ */
+ public void addContainerProvider(ContainerProvider provider) {
+ if (!containerProviders.contains(provider)) {
+ containerProviders.add(provider);
+ }
+ }
+
+ /**
+ * clears the registered ConfigurationProviders. this method will call destroy() on each of the registered
+ * ConfigurationProviders
+ *
+ * @see com.opensymphony.xwork2.config.ConfigurationProvider#destroy
+ * @deprecated Since 2.1, use {@link #clearContainerProviders()}
+ */
+ @Deprecated public void clearConfigurationProviders() {
+ clearContainerProviders();
+ }
+
+ public void clearContainerProviders() {
+ for (ContainerProvider containerProvider : containerProviders) {
+ try {
+ containerProvider.destroy();
+ }
+ catch(Exception e) {
+ LOG.warn("error while destroying container provider ["+containerProvider+"]", e);
+ }
+ }
+ containerProviders.clear();
+ }
+
+ /**
+ * Destroy its managing Configuration instance
+ */
+ public synchronized void destroyConfiguration() {
+ clearConfigurationProviders(); // let's destroy the ConfigurationProvider first
+ containerProviders = new CopyOnWriteArrayList();
+ if (configuration != null)
+ configuration.destroy(); // let's destroy it first, before nulling it.
+ configuration = null;
+ }
+
+
+ /**
+ * Reloads the Configuration files if the configuration files indicate that they need to be reloaded.
+ */
+ public synchronized void conditionalReload() {
+ if (FileManager.isReloadingConfigs()) {
+ boolean reload;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Checking ConfigurationProviders for reload.");
+ }
+
+ reload = false;
+
+ List providers = getContainerProviders();
+ for (ContainerProvider provider : providers) {
+ if (provider.needsReload()) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info("Detected container provider "+provider+" needs to be reloaded. Reloading all providers.");
+ }
+ reload = true;
+
+ //break;
+ }
+ }
+
+ if (packageProviders != null && reload) {
+ for (PackageProvider provider : packageProviders) {
+ if (provider.needsReload()) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info("Detected package provider "+provider+" needs to be reloaded. Reloading all providers.");
+ }
+ reload = true;
+
+ //break;
+ }
+ }
+ }
+
+ if (reload) {
+ for (ContainerProvider containerProvider : containerProviders) {
+ try {
+ containerProvider.destroy();
+ }
+ catch(Exception e) {
+ LOG.warn("error while destroying configuration provider ["+containerProvider+"]", e);
+ }
+ }
+ packageProviders = configuration.reloadContainer(providers);
+ }
+ }
+ }
+
+ public synchronized void reload() {
+ packageProviders = getConfiguration().reloadContainer(getContainerProviders());
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationProvider.java
new file mode 100644
index 000000000..146532bf5
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationProvider.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+/**
+ * Interface to be implemented by all forms of XWork configuration classes.
+ */
+public interface ConfigurationProvider extends ContainerProvider, PackageProvider {
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java
new file mode 100644
index 000000000..679f6d86c
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.config.entities.PackageConfig;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+/**
+ * ConfigurationUtil
+ *
+ * @author Jason Carreira
+ * Created May 23, 2003 11:22:49 PM
+ */
+public class ConfigurationUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ConfigurationUtil.class);
+
+
+ private ConfigurationUtil() {
+ }
+
+
+ public static List buildParentsFromString(Configuration configuration, String parent) {
+ if ((parent == null) || ("".equals(parent))) {
+ return Collections.emptyList();
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer(parent, ", ");
+ List parents = new ArrayList();
+
+ while (tokenizer.hasMoreTokens()) {
+ String parentName = tokenizer.nextToken().trim();
+
+ if (!"".equals(parentName)) {
+ PackageConfig parentPackageContext = configuration.getPackageConfig(parentName);
+
+ if (parentPackageContext != null) {
+ parents.add(parentPackageContext);
+ }
+ }
+ }
+
+ return parents;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ContainerProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ContainerProvider.java
new file mode 100644
index 000000000..de943f0a1
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ContainerProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+
+
+/**
+ * Provides beans and constants/properties for the Container
+ *
+ * @since 2.1
+ */
+public interface ContainerProvider {
+
+ /**
+ * Called before removed from the configuration manager
+ */
+ public void destroy();
+
+ /**
+ * Initializes with the configuration
+ * @param configuration The configuration
+ * @throws ConfigurationException If anything goes wrong
+ */
+ public void init(Configuration configuration) throws ConfigurationException;
+
+ /**
+ * Tells whether the ContainerProvider should reload its configuration
+ *
+ * @return true , whether the ContainerProvider should reload its configuration, false otherwise.
+ */
+ public boolean needsReload();
+
+ /**
+ * Registers beans and properties for the Container
+ *
+ * @param builder The builder to register beans with
+ * @param props The properties to register constants with
+ * @throws ConfigurationException If anything goes wrong
+ */
+ public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException;
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/PackageProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/PackageProvider.java
new file mode 100644
index 000000000..dd0dfaeaa
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/PackageProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+/**
+ * Provides configuration packages. The separate init and loadPackages calls are due to the need to
+ * preserve backwards compatibility with the 2.0 {@link ConfigurationProvider} interface
+ *
+ * @since 2.1
+ */
+public interface PackageProvider {
+
+ /**
+ * Initializes with the configuration
+ * @param configuration The configuration
+ * @throws ConfigurationException If anything goes wrong
+ */
+ public void init(Configuration configuration) throws ConfigurationException;
+
+ /**
+ * Tells whether the PackageProvider should reload its configuration
+ *
+ * @return true , whether the PackageProvider should reload its configuration, false otherwise.
+ */
+ public boolean needsReload();
+
+ /**
+ * Loads the packages for the configuration.
+ * @throws ConfigurationException
+ */
+ public void loadPackages() throws ConfigurationException;
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ReferenceResolverException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ReferenceResolverException.java
new file mode 100644
index 000000000..00f1adb7b
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ReferenceResolverException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.XWorkException;
+
+
+/**
+ * Exception when a reference can't be resolved.
+ *
+ * @author Mike
+ */
+public class ReferenceResolverException extends XWorkException {
+
+ public ReferenceResolverException() {
+ super();
+ }
+
+ public ReferenceResolverException(String s) {
+ super(s);
+ }
+
+ public ReferenceResolverException(String s, Throwable cause) {
+ super(s, cause);
+ }
+
+ public ReferenceResolverException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/RuntimeConfiguration.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/RuntimeConfiguration.java
new file mode 100644
index 000000000..f24c766cc
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/RuntimeConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+import java.io.Serializable;
+import java.util.Map;
+
+
+/**
+ * RuntimeConfiguration
+ *
+ * @author Jason Carreira
+ * Created Feb 25, 2003 10:56:02 PM
+ */
+public interface RuntimeConfiguration extends Serializable {
+
+ /**
+ * get the fully expanded ActionConfig for a specified namespace and (action) name
+ *
+ * @param namespace the namespace of the Action. if this is null, then the empty namespace, "", will be used
+ * @param name the name of the Action. may not be null.
+ * @return the requested ActionConfig or null if there was no ActionConfig associated with the specified namespace
+ * and name
+ */
+ ActionConfig getActionConfig(String namespace, String name);
+
+ /**
+ * returns a Map of all the registered ActionConfigs. Again, these ActionConfigs are fully expanded so that any
+ * inherited interceptors, results, etc. will be included
+ *
+ * @return a Map of Map keyed by namespace and name respectively such that
+ *
+ * ActionConfig config = (ActionConfig)((Map)getActionConfigs.get(namespace)).get(name);
+ *
+ * should return a valid config for valid namespace/name pairs
+ */
+ Map> getActionConfigs();
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ActionConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ActionConfig.java
new file mode 100644
index 000000000..94f0b0ee7
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ActionConfig.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+
+import java.io.Serializable;
+import java.util.*;
+
+import org.apache.commons.lang.StringUtils;
+
+
+/**
+ * Contains everything needed to configure and execute an action:
+ *
+ * methodName - the method name to execute on the action. If this is null, the Action will be cast to the Action
+ * Interface and the execute() method called
+ * clazz - the class name for the action
+ * params - the params to be set for this action just before execution
+ * results - the result map {String -> View class}
+ * resultParameters - params for results {String -> Map}
+ * typeConverter - the Ognl TypeConverter to use when getting/setting properties
+ *
+ *
+ * @author Mike
+ * @author Rainer Hermanns
+ * @version $Revision$
+ */
+public class ActionConfig extends Located implements Serializable {
+
+ public static final String WILDCARD = "*";
+
+ protected List interceptors; // a list of interceptorMapping Objects eg. List
+ protected Map params;
+ protected Map results;
+ protected List exceptionMappings;
+ protected String className;
+ protected String methodName;
+ protected String packageName;
+ protected String name;
+ protected Set allowedMethods;
+
+ protected ActionConfig(String packageName, String name, String className) {
+ this.packageName = packageName;
+ this.name = name;
+ this.className = className;
+ params = new LinkedHashMap();
+ results = new LinkedHashMap();
+ interceptors = new ArrayList();
+ exceptionMappings = new ArrayList();
+ allowedMethods = new HashSet();
+ allowedMethods.add(WILDCARD);
+ }
+
+ /**
+ * Clones an ActionConfig, copying data into new maps and lists
+ * @param orig The ActionConfig to clone
+ * @Since 2.1
+ */
+ protected ActionConfig(ActionConfig orig) {
+ this.name = orig.name;
+ this.className = orig.className;
+ this.methodName = orig.methodName;
+ this.packageName = orig.packageName;
+ this.params = new LinkedHashMap(orig.params);
+ this.interceptors = new ArrayList(orig.interceptors);
+ this.results = new LinkedHashMap(orig.results);
+ this.exceptionMappings = new ArrayList(orig.exceptionMappings);
+ this.allowedMethods = new HashSet(orig.allowedMethods);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public List getExceptionMappings() {
+ return exceptionMappings;
+ }
+
+ public List getInterceptors() {
+ return interceptors;
+ }
+
+ public Set getAllowedMethods() {
+ return allowedMethods;
+ }
+
+ /**
+ * Returns name of the action method
+ *
+ * @return name of the method to execute
+ */
+ public String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * @return Returns the packageName.
+ */
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public Map getParams() {
+ return params;
+ }
+
+ public Map getResults() {
+ return results;
+ }
+
+ public boolean isAllowedMethod(String method) {
+ if (allowedMethods.size() == 1 && WILDCARD.equals(allowedMethods.iterator().next())) {
+ return true;
+ } else {
+ return allowedMethods.contains(method);
+ }
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ActionConfig)) {
+ return false;
+ }
+
+ final ActionConfig actionConfig = (ActionConfig) o;
+
+ if ((className != null) ? (!className.equals(actionConfig.className)) : (actionConfig.className != null)) {
+ return false;
+ }
+
+ if ((name != null) ? (!name.equals(actionConfig.name)) : (actionConfig.name != null)) {
+ return false;
+ }
+
+ if ((interceptors != null) ? (!interceptors.equals(actionConfig.interceptors)) : (actionConfig.interceptors != null))
+ {
+ return false;
+ }
+
+ if ((methodName != null) ? (!methodName.equals(actionConfig.methodName)) : (actionConfig.methodName != null)) {
+ return false;
+ }
+
+ if ((params != null) ? (!params.equals(actionConfig.params)) : (actionConfig.params != null)) {
+ return false;
+ }
+
+ if ((results != null) ? (!results.equals(actionConfig.results)) : (actionConfig.results != null)) {
+ return false;
+ }
+
+ if ((allowedMethods != null) ? (!allowedMethods.equals(actionConfig.allowedMethods)) : (actionConfig.allowedMethods != null)) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ @Override public int hashCode() {
+ int result;
+ result = (interceptors != null ? interceptors.hashCode() : 0);
+ result = 31 * result + (params != null ? params.hashCode() : 0);
+ result = 31 * result + (results != null ? results.hashCode() : 0);
+ result = 31 * result + (exceptionMappings != null ? exceptionMappings.hashCode() : 0);
+ result = 31 * result + (className != null ? className.hashCode() : 0);
+ result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
+ result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + (allowedMethods != null ? allowedMethods.hashCode() : 0);
+ return result;
+ }
+
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{ActionConfig ");
+ sb.append(name).append(" (");
+ sb.append(className);
+ if (methodName != null) {
+ sb.append(".").append(methodName).append("()");
+ }
+ sb.append(")");
+ sb.append(" - ").append(location);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * The builder for this object. An instance of this object is the only way to construct a new instance. The
+ * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
+ * After setting any values you need, call the {@link #build()} method to create the object.
+ */
+ public static class Builder implements InterceptorListHolder{
+
+ private ActionConfig target;
+
+ public Builder(ActionConfig toClone) {
+ target = new ActionConfig(toClone);
+ }
+
+ public Builder(String packageName, String name, String className) {
+ target = new ActionConfig(packageName, name, className);
+ }
+
+ public Builder packageName(String name) {
+ target.packageName = name;
+ return this;
+ }
+
+ public Builder name(String name) {
+ target.name = name;
+ return this;
+ }
+
+ public Builder className(String name) {
+ target.className = name;
+ return this;
+ }
+
+ public Builder defaultClassName(String name) {
+ if (StringUtils.isEmpty(target.className)) {
+ target.className = name;
+ }
+ return this;
+ }
+
+ public Builder methodName(String method) {
+ target.methodName = method;
+ return this;
+ }
+
+ public Builder addExceptionMapping(ExceptionMappingConfig exceptionMapping) {
+ target.exceptionMappings.add(exceptionMapping);
+ return this;
+ }
+
+ public Builder addExceptionMappings(Collection extends ExceptionMappingConfig> mappings) {
+ target.exceptionMappings.addAll(mappings);
+ return this;
+ }
+
+ public Builder exceptionMappings(Collection extends ExceptionMappingConfig> mappings) {
+ target.exceptionMappings.clear();
+ target.exceptionMappings.addAll(mappings);
+ return this;
+ }
+
+ public Builder addInterceptor(InterceptorMapping interceptor) {
+ target.interceptors.add(interceptor);
+ return this;
+ }
+
+ public Builder addInterceptors(List interceptors) {
+ target.interceptors.addAll(interceptors);
+ return this;
+ }
+
+ public Builder interceptors(List interceptors) {
+ target.interceptors.clear();
+ target.interceptors.addAll(interceptors);
+ return this;
+ }
+
+ public Builder addParam(String name, String value) {
+ target.params.put(name, value);
+ return this;
+ }
+
+ public Builder addParams(Map params) {
+ target.params.putAll(params);
+ return this;
+ }
+
+ public Builder addResultConfig(ResultConfig resultConfig) {
+ target.results.put(resultConfig.getName(), resultConfig);
+ return this;
+ }
+
+ public Builder addResultConfigs(Collection configs) {
+ for (ResultConfig rc : configs) {
+ target.results.put(rc.getName(), rc);
+ }
+ return this;
+ }
+
+ public Builder addResultConfigs(Map configs) {
+ target.results.putAll(configs);
+ return this;
+ }
+
+ public Builder addAllowedMethod(String methodName) {
+ target.allowedMethods.add(methodName);
+ return this;
+ }
+
+ public Builder addAllowedMethod(Collection methods) {
+ target.allowedMethods.addAll(methods);
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public ActionConfig build() {
+ target.params = Collections.unmodifiableMap(target.params);
+ target.results = Collections.unmodifiableMap(target.results);
+ target.interceptors = Collections.unmodifiableList(target.interceptors);
+ target.exceptionMappings = Collections.unmodifiableList(target.exceptionMappings);
+ target.allowedMethods = Collections.unmodifiableSet(target.allowedMethods);
+ ActionConfig result = target;
+ target = new ActionConfig(target);
+ return result;
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ExceptionMappingConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ExceptionMappingConfig.java
new file mode 100644
index 000000000..2d09d5ddb
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ExceptionMappingConfig.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Configuration for exception mapping.
+ *
+ * @author Rainer Hermanns
+ * @author Matthew E. Porter (matthew dot porter at metissian dot com)
+ */
+public class ExceptionMappingConfig extends Located implements Serializable {
+
+ private String name;
+ private String exceptionClassName;
+ private String result;
+ private Map params;
+
+
+ protected ExceptionMappingConfig(String name, String exceptionClassName, String result) {
+ this.name = name;
+ this.exceptionClassName = exceptionClassName;
+ this.result = result;
+ this.params = new LinkedHashMap();
+ }
+
+ protected ExceptionMappingConfig(ExceptionMappingConfig target) {
+ this.name = target.name;
+ this.exceptionClassName = target.exceptionClassName;
+ this.result = target.result;
+ this.params = new LinkedHashMap(target.params);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getExceptionClassName() {
+ return exceptionClassName;
+ }
+
+ public String getResult() {
+ return result;
+ }
+
+ public Map getParams() {
+ return params;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ExceptionMappingConfig)) {
+ return false;
+ }
+
+ final ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) o;
+
+ if ((name != null) ? (!name.equals(exceptionMappingConfig.name)) : (exceptionMappingConfig.name != null)) {
+ return false;
+ }
+
+ if ((exceptionClassName != null) ? (!exceptionClassName.equals(exceptionMappingConfig.exceptionClassName)) : (exceptionMappingConfig.exceptionClassName != null))
+ {
+ return false;
+ }
+
+ if ((result != null) ? (!result.equals(exceptionMappingConfig.result)) : (exceptionMappingConfig.result != null))
+ {
+ return false;
+ }
+
+ if ((params != null) ? (!params.equals(exceptionMappingConfig.params)) : (exceptionMappingConfig.params != null))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode;
+ hashCode = ((name != null) ? name.hashCode() : 0);
+ hashCode = (29 * hashCode) + ((exceptionClassName != null) ? exceptionClassName.hashCode() : 0);
+ hashCode = (29 * hashCode) + ((result != null) ? result.hashCode() : 0);
+ hashCode = (29 * hashCode) + ((params != null) ? params.hashCode() : 0);
+
+ return hashCode;
+ }
+
+ /**
+ * The builder for this object. An instance of this object is the only way to construct a new instance. The
+ * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
+ * After setting any values you need, call the {@link #build()} method to create the object.
+ */
+ public static class Builder{
+
+ private ExceptionMappingConfig target;
+
+ public Builder(ExceptionMappingConfig toClone) {
+ target = new ExceptionMappingConfig(toClone);
+ }
+
+ public Builder(String name, String exceptionClassName, String result) {
+ target = new ExceptionMappingConfig(name, exceptionClassName, result);
+ }
+
+ public Builder name(String name) {
+ target.name = name;
+ return this;
+ }
+
+ public Builder exceptionClassName(String name) {
+ target.exceptionClassName = name;
+ return this;
+ }
+
+ public Builder result(String result) {
+ target.result = result;
+ return this;
+ }
+
+ public Builder addParam(String name, String value) {
+ target.params.put(name, value);
+ return this;
+ }
+
+ public Builder addParams(Map params) {
+ target.params.putAll(params);
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public ExceptionMappingConfig build() {
+ target.params = Collections.unmodifiableMap(target.params);
+ ExceptionMappingConfig result = target;
+ target = new ExceptionMappingConfig(target);
+ return result;
+ }
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorConfig.java
new file mode 100644
index 000000000..2b951c01b
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorConfig.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Configuration for Interceptors.
+ *
+ * In the xml configuration file this is defined as the interceptors tag.
+ *
+ * @author Mike
+ */
+public class InterceptorConfig extends Located implements Serializable {
+
+ Map params;
+ String className;
+ String name;
+
+
+ protected InterceptorConfig(String name, String className) {
+ this.params = new LinkedHashMap();
+ this.name = name;
+ this.className = className;
+ }
+
+ protected InterceptorConfig(InterceptorConfig orig) {
+ this.name = orig.name;
+ this.className = orig.className;
+ this.params = new LinkedHashMap(orig.params);
+ }
+
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map getParams() {
+ return params;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof InterceptorConfig)) {
+ return false;
+ }
+
+ final InterceptorConfig interceptorConfig = (InterceptorConfig) o;
+
+ if ((className != null) ? (!className.equals(interceptorConfig.className)) : (interceptorConfig.className != null))
+ {
+ return false;
+ }
+
+ if ((name != null) ? (!name.equals(interceptorConfig.name)) : (interceptorConfig.name != null)) {
+ return false;
+ }
+
+ if ((params != null) ? (!params.equals(interceptorConfig.params)) : (interceptorConfig.params != null)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = ((name != null) ? name.hashCode() : 0);
+ result = (29 * result) + ((className != null) ? className.hashCode() : 0);
+ result = (29 * result) + ((params != null) ? params.hashCode() : 0);
+
+ return result;
+ }
+
+ /**
+ * The builder for this object. An instance of this object is the only way to construct a new instance. The
+ * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
+ * After setting any values you need, call the {@link #build()} method to create the object.
+ */
+ public static final class Builder {
+ private InterceptorConfig target;
+
+ public Builder(String name, String className) {
+ target = new InterceptorConfig(name, className);
+ }
+
+ public Builder(InterceptorConfig orig) {
+ target = new InterceptorConfig(orig);
+ }
+
+ public Builder name(String name) {
+ target.name = name;
+ return this;
+ }
+
+ public Builder className(String name) {
+ target.className = name;
+ return this;
+ }
+
+ public Builder addParam(String name, String value) {
+ target.params.put(name, value);
+ return this;
+ }
+
+ public Builder addParams(Map params) {
+ target.params.putAll(params);
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public InterceptorConfig build() {
+ target.params = Collections.unmodifiableMap(target.params);
+ InterceptorConfig result = target;
+ target = new InterceptorConfig(target);
+ return result;
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java
new file mode 100644
index 000000000..432311626
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import java.util.List;
+
+/**
+ * InterceptorListHolder
+ *
+ * @author Jason Carreira
+ * Created Jun 1, 2003 1:02:48 AM
+ */
+public interface InterceptorListHolder {
+
+ InterceptorListHolder addInterceptor(InterceptorMapping interceptor);
+
+ InterceptorListHolder addInterceptors(List interceptors);
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java
new file mode 100644
index 000000000..aa7496095
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java
@@ -0,0 +1,14 @@
+package com.opensymphony.xwork2.config.entities;
+
+/**
+ * Defines an object that can be used to retrieve interceptor configuration
+ */
+public interface InterceptorLocator {
+
+ /**
+ * Gets an interceptor configuration object.
+ * @param name The interceptor or interceptor stack name
+ * @return Either an {@link InterceptorConfig} or {@link InterceptorStackConfig} object
+ */
+ Object getInterceptorConfig(String name);
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java
new file mode 100644
index 000000000..2871e8c6a
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.interceptor.Interceptor;
+
+import java.io.Serializable;
+
+/**
+ * InterceptorMapping
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+public class InterceptorMapping implements Serializable {
+
+ private String name;
+ private Interceptor interceptor;
+
+ public InterceptorMapping(String name, Interceptor interceptor) {
+ this.name = name;
+ this.interceptor = interceptor;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Interceptor getInterceptor() {
+ return interceptor;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final InterceptorMapping that = (InterceptorMapping) o;
+
+ if (name != null ? !name.equals(that.name) : that.name != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = (name != null ? name.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java
new file mode 100644
index 000000000..bb833720a
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Configuration for InterceptorStack.
+ *
+ * In the xml configuration file this is defined as the interceptor-stack tag.
+ *
+ * @author Mike
+ * @author Rainer Hermanns
+ */
+public class InterceptorStackConfig extends Located implements Serializable {
+
+ private static final long serialVersionUID = 2897260918170270343L;
+
+ /**
+ * A list of InterceptorMapping object
+ */
+ private List interceptors;
+ private String name;
+
+
+ /**
+ * Creates an InterceptorStackConfig object.
+ */
+ protected InterceptorStackConfig() {
+ this.interceptors = new ArrayList();
+ }
+
+ /**
+ * Creates an InterceptorStackConfig object with a particular name.
+ *
+ * @param name
+ */
+ protected InterceptorStackConfig(InterceptorStackConfig orig) {
+ this.name = orig.name;
+ this.interceptors = new ArrayList(orig.interceptors);
+ }
+
+
+ /**
+ * Returns a Collection of InterceptorMapping objects.
+ *
+ * @return
+ */
+ public Collection getInterceptors() {
+ return interceptors;
+ }
+
+ /**
+ * Get the name of this interceptor stack configuration.
+ *
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * An InterceptorStackConfig object is equals with o only if
+ *
+ * o is an InterceptorStackConfig object
+ * both names are equals
+ * all of their InterceptorMappings are equals
+ *
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof InterceptorStackConfig)) {
+ return false;
+ }
+
+ final InterceptorStackConfig interceptorStackConfig = (InterceptorStackConfig) o;
+
+ if ((interceptors != null) ? (!interceptors.equals(interceptorStackConfig.interceptors)) : (interceptorStackConfig.interceptors != null)) {
+ return false;
+ }
+
+ if ((name != null) ? (!name.equals(interceptorStackConfig.name)) : (interceptorStackConfig.name != null)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generate hashcode based on InterceptorStackConfig's name and its
+ * InterceptorMappings.
+ */
+ @Override
+ public int hashCode() {
+ int result;
+ result = ((name != null) ? name.hashCode() : 0);
+ result = (29 * result) + ((interceptors != null) ? interceptors.hashCode() : 0);
+
+ return result;
+ }
+
+ /**
+ * The builder for this object. An instance of this object is the only way to construct a new instance. The
+ * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
+ * After setting any values you need, call the {@link #build()} method to create the object.
+ */
+ public static class Builder implements InterceptorListHolder {
+ private InterceptorStackConfig target;
+
+ public Builder(String name) {
+ target = new InterceptorStackConfig();
+ target.name = name;
+ }
+
+ public Builder name(String name) {
+ target.name = name;
+ return this;
+ }
+
+ /**
+ * Add an InterceptorMapping object.
+ */
+ public Builder addInterceptor(InterceptorMapping interceptor) {
+ target.interceptors.add(interceptor);
+ return this;
+ }
+
+ /**
+ * Add a List of InterceptorMapping objects.
+ */
+ public Builder addInterceptors(List interceptors) {
+ target.interceptors.addAll(interceptors);
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public InterceptorStackConfig build() {
+ target.interceptors = Collections.unmodifiableList(target.interceptors);
+ InterceptorStackConfig result = target;
+ target = new InterceptorStackConfig(target);
+ return result;
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
new file mode 100644
index 000000000..6ca513525
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.*;
+
+
+/**
+ * Configuration for Package.
+ *
+ * In the xml configuration file this is defined as the package tag.
+ *
+ * @author Rainer Hermanns
+ * @version $Revision$
+ */
+public class PackageConfig extends Located implements Comparable, Serializable, InterceptorLocator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PackageConfig.class);
+
+ private Map actionConfigs;
+ private Map globalResultConfigs;
+ private Map interceptorConfigs;
+ private Map resultTypeConfigs;
+ private List globalExceptionMappingConfigs;
+ private List parents;
+ private String defaultInterceptorRef;
+ private String defaultActionRef;
+ private String defaultResultType;
+ private String defaultClassRef;
+ private String name;
+ private String namespace = "";
+ private boolean isAbstract = false;
+ private boolean needsRefresh;
+
+
+ protected PackageConfig(String name) {
+ this.name = name;
+ actionConfigs = new LinkedHashMap();
+ globalResultConfigs = new LinkedHashMap();
+ interceptorConfigs = new LinkedHashMap();
+ resultTypeConfigs = new LinkedHashMap();
+ globalExceptionMappingConfigs = new ArrayList();
+ parents = new ArrayList();
+ }
+
+
+ protected PackageConfig(PackageConfig orig) {
+ this.defaultInterceptorRef = orig.defaultInterceptorRef;
+ this.defaultActionRef = orig.defaultActionRef;
+ this.defaultResultType = orig.defaultResultType;
+ this.defaultClassRef = orig.defaultClassRef;
+ this.name = orig.name;
+ this.namespace = orig.namespace;
+ this.isAbstract = orig.isAbstract;
+ this.needsRefresh = orig.needsRefresh;
+ this.actionConfigs = new LinkedHashMap(orig.actionConfigs);
+ this.globalResultConfigs = new LinkedHashMap(orig.globalResultConfigs);
+ this.interceptorConfigs = new LinkedHashMap(orig.interceptorConfigs);
+ this.resultTypeConfigs = new LinkedHashMap(orig.resultTypeConfigs);
+ this.globalExceptionMappingConfigs = new ArrayList(orig.globalExceptionMappingConfigs);
+ this.parents = new ArrayList(orig.parents);
+ }
+
+ public boolean isAbstract() {
+ return isAbstract;
+ }
+
+ public Map getActionConfigs() {
+ return actionConfigs;
+ }
+
+ /**
+ * returns the Map of all the ActionConfigs available in the current package.
+ * ActionConfigs defined in ancestor packages will be included in this Map.
+ *
+ * @return a Map of ActionConfig Objects with the action name as the key
+ * @see ActionConfig
+ */
+ public Map getAllActionConfigs() {
+ Map retMap = new LinkedHashMap();
+
+ if (!parents.isEmpty()) {
+ for (PackageConfig parent : parents) {
+ retMap.putAll(parent.getAllActionConfigs());
+ }
+ }
+
+ retMap.putAll(getActionConfigs());
+
+ return retMap;
+ }
+
+ /**
+ * returns the Map of all the global ResultConfigs available in the current package.
+ * Global ResultConfigs defined in ancestor packages will be included in this Map.
+ *
+ * @return a Map of Result Objects with the result name as the key
+ * @see ResultConfig
+ */
+ public Map getAllGlobalResults() {
+ Map retMap = new LinkedHashMap();
+
+ if (!parents.isEmpty()) {
+ for (PackageConfig parentConfig : parents) {
+ retMap.putAll(parentConfig.getAllGlobalResults());
+ }
+ }
+
+ retMap.putAll(getGlobalResultConfigs());
+
+ return retMap;
+ }
+
+ /**
+ * returns the Map of all InterceptorConfigs and InterceptorStackConfigs available in the current package.
+ * InterceptorConfigs defined in ancestor packages will be included in this Map.
+ *
+ * @return a Map of InterceptorConfig and InterceptorStackConfig Objects with the ref-name as the key
+ * @see InterceptorConfig
+ * @see InterceptorStackConfig
+ */
+ public Map getAllInterceptorConfigs() {
+ Map retMap = new LinkedHashMap();
+
+ if (!parents.isEmpty()) {
+ for (PackageConfig parentContext : parents) {
+ retMap.putAll(parentContext.getAllInterceptorConfigs());
+ }
+ }
+
+ retMap.putAll(getInterceptorConfigs());
+
+ return retMap;
+ }
+
+ /**
+ * returns the Map of all the ResultTypeConfigs available in the current package.
+ * ResultTypeConfigs defined in ancestor packages will be included in this Map.
+ *
+ * @return a Map of ResultTypeConfig Objects with the result type name as the key
+ * @see ResultTypeConfig
+ */
+ public Map getAllResultTypeConfigs() {
+ Map retMap = new LinkedHashMap();
+
+ if (!parents.isEmpty()) {
+ for (PackageConfig parentContext : parents) {
+ retMap.putAll(parentContext.getAllResultTypeConfigs());
+ }
+ }
+
+ retMap.putAll(getResultTypeConfigs());
+
+ return retMap;
+ }
+
+ /**
+ * returns the List of all the ExceptionMappingConfigs available in the current package.
+ * ExceptionMappingConfigs defined in ancestor packages will be included in this list.
+ *
+ * @return a List of ExceptionMappingConfigs Objects with the result type name as the key
+ * @see ExceptionMappingConfig
+ */
+ public List getAllExceptionMappingConfigs() {
+ List allExceptionMappings = new ArrayList();
+
+ if (!parents.isEmpty()) {
+ for (PackageConfig parentContext : parents) {
+ allExceptionMappings.addAll(parentContext.getAllExceptionMappingConfigs());
+ }
+ }
+
+ allExceptionMappings.addAll(getGlobalExceptionMappingConfigs());
+
+ return allExceptionMappings;
+ }
+
+
+ public String getDefaultInterceptorRef() {
+ return defaultInterceptorRef;
+ }
+
+ public String getDefaultActionRef() {
+ return defaultActionRef;
+ }
+
+ public String getDefaultClassRef() {
+ if((defaultClassRef == null) && !parents.isEmpty()) {
+ for (PackageConfig parent : parents) {
+ String parentDefault = parent.getDefaultClassRef();
+ if (parentDefault != null) {
+ return parentDefault;
+ }
+ }
+ }
+ return defaultClassRef;
+ }
+
+ /**
+ * Returns the default result type for this package.
+ */
+ public String getDefaultResultType() {
+ return defaultResultType;
+ }
+
+ /**
+ * gets the default interceptor-ref name. If this is not set on this PackageConfig, it searches the parent
+ * PackageConfigs in order until it finds one.
+ */
+ public String getFullDefaultInterceptorRef() {
+ if ((defaultInterceptorRef == null) && !parents.isEmpty()) {
+ for (PackageConfig parent : parents) {
+ String parentDefault = parent.getFullDefaultInterceptorRef();
+
+ if (parentDefault != null) {
+ return parentDefault;
+ }
+ }
+ }
+
+ return defaultInterceptorRef;
+ }
+
+ /**
+ * gets the default action-ref name. If this is not set on this PackageConfig, it searches the parent
+ * PackageConfigs in order until it finds one.
+ */
+ public String getFullDefaultActionRef() {
+ if ((defaultActionRef == null) && !parents.isEmpty()) {
+ for (PackageConfig parent : parents) {
+ String parentDefault = parent.getFullDefaultActionRef();
+
+ if (parentDefault != null) {
+ return parentDefault;
+ }
+ }
+ }
+ return defaultActionRef;
+ }
+
+ /**
+ * Returns the default result type for this package.
+ *
+ * If there is no default result type, but this package has parents - we will try to
+ * look up the default result type of a parent.
+ */
+ public String getFullDefaultResultType() {
+ if ((defaultResultType == null) && !parents.isEmpty()) {
+ for (PackageConfig parent : parents) {
+ String parentDefault = parent.getFullDefaultResultType();
+
+ if (parentDefault != null) {
+ return parentDefault;
+ }
+ }
+ }
+
+ return defaultResultType;
+ }
+
+ /**
+ * gets the global ResultConfigs local to this package
+ *
+ * @return a Map of ResultConfig objects keyed by result name
+ * @see ResultConfig
+ */
+ public Map getGlobalResultConfigs() {
+ return globalResultConfigs;
+ }
+
+ /**
+ * gets the InterceptorConfigs and InterceptorStackConfigs local to this package
+ *
+ * @return a Map of InterceptorConfig and InterceptorStackConfig objects keyed by ref-name
+ * @see InterceptorConfig
+ * @see InterceptorStackConfig
+ */
+ public Map getInterceptorConfigs() {
+ return interceptorConfigs;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public List getParents() {
+ return new ArrayList(parents);
+ }
+
+ /**
+ * gets the ResultTypeConfigs local to this package
+ *
+ * @return a Map of ResultTypeConfig objects keyed by result name
+ * @see ResultTypeConfig
+ */
+ public Map getResultTypeConfigs() {
+ return resultTypeConfigs;
+ }
+
+
+ public boolean isNeedsRefresh() {
+ return needsRefresh;
+ }
+
+ /**
+ * gets the ExceptionMappingConfigs local to this package
+ *
+ * @return a Map of ExceptionMappingConfig objects keyed by result name
+ * @see ExceptionMappingConfig
+ */
+ public List getGlobalExceptionMappingConfigs() {
+ return globalExceptionMappingConfigs;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof PackageConfig)) {
+ return false;
+ }
+
+ final PackageConfig packageConfig = (PackageConfig) o;
+
+ if (isAbstract != packageConfig.isAbstract) {
+ return false;
+ }
+
+ if ((actionConfigs != null) ? (!actionConfigs.equals(packageConfig.actionConfigs)) : (packageConfig.actionConfigs != null))
+ {
+ return false;
+ }
+
+ if ((defaultResultType != null) ? (!defaultResultType.equals(packageConfig.defaultResultType)) : (packageConfig.defaultResultType != null))
+ {
+ return false;
+ }
+
+ if ((defaultClassRef != null) ? (!defaultClassRef.equals(packageConfig.defaultClassRef)) : (packageConfig.defaultClassRef != null))
+ {
+ return false;
+ }
+
+ if ((globalResultConfigs != null) ? (!globalResultConfigs.equals(packageConfig.globalResultConfigs)) : (packageConfig.globalResultConfigs != null))
+ {
+ return false;
+ }
+
+ if ((interceptorConfigs != null) ? (!interceptorConfigs.equals(packageConfig.interceptorConfigs)) : (packageConfig.interceptorConfigs != null))
+ {
+ return false;
+ }
+
+ if ((name != null) ? (!name.equals(packageConfig.name)) : (packageConfig.name != null)) {
+ return false;
+ }
+
+ if ((namespace != null) ? (!namespace.equals(packageConfig.namespace)) : (packageConfig.namespace != null)) {
+ return false;
+ }
+
+ if ((parents != null) ? (!parents.equals(packageConfig.parents)) : (packageConfig.parents != null)) {
+ return false;
+ }
+
+ if ((resultTypeConfigs != null) ? (!resultTypeConfigs.equals(packageConfig.resultTypeConfigs)) : (packageConfig.resultTypeConfigs != null))
+ {
+ return false;
+ }
+
+ if ((globalExceptionMappingConfigs != null) ? (!globalExceptionMappingConfigs.equals(packageConfig.globalExceptionMappingConfigs)) : (packageConfig.globalExceptionMappingConfigs != null))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = ((name != null) ? name.hashCode() : 0);
+ result = (29 * result) + ((parents != null) ? parents.hashCode() : 0);
+ result = (29 * result) + ((actionConfigs != null) ? actionConfigs.hashCode() : 0);
+ result = (29 * result) + ((globalResultConfigs != null) ? globalResultConfigs.hashCode() : 0);
+ result = (29 * result) + ((interceptorConfigs != null) ? interceptorConfigs.hashCode() : 0);
+ result = (29 * result) + ((resultTypeConfigs != null) ? resultTypeConfigs.hashCode() : 0);
+ result = (29 * result) + ((globalExceptionMappingConfigs != null) ? globalExceptionMappingConfigs.hashCode() : 0);
+ result = (29 * result) + ((defaultResultType != null) ? defaultResultType.hashCode() : 0);
+ result = (29 * result) + ((defaultClassRef != null) ? defaultClassRef.hashCode() : 0);
+ result = (29 * result) + ((namespace != null) ? namespace.hashCode() : 0);
+ result = (29 * result) + (isAbstract ? 1 : 0);
+
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "{PackageConfig Name:" + name + " namespace:" + namespace + " parents:" + parents + "}";
+ }
+
+ public int compareTo(Object o) {
+ PackageConfig other = (PackageConfig) o;
+ String full = namespace + "!" + name;
+ String otherFull = other.namespace + "!" + other.name;
+
+ // note, this isn't perfect (could come from different parents), but it is "good enough"
+ return full.compareTo(otherFull);
+ }
+
+ public Object getInterceptorConfig(String name) {
+ return getAllInterceptorConfigs().get(name);
+ }
+
+ /**
+ * The builder for this object. An instance of this object is the only way to construct a new instance. The
+ * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
+ * After setting any values you need, call the {@link #build()} method to create the object.
+ */
+ public static class Builder implements InterceptorLocator {
+
+ private PackageConfig target;
+
+ public Builder(String name) {
+ target = new PackageConfig(name);
+ }
+
+ public Builder(PackageConfig config) {
+ target = new PackageConfig(config);
+ }
+
+ public Builder name(String name) {
+ target.name = name;
+ return this;
+ }
+
+ public Builder isAbstract(boolean isAbstract) {
+ target.isAbstract = isAbstract;
+ return this;
+ }
+
+ public Builder defaultInterceptorRef(String name) {
+ target.defaultInterceptorRef = name;
+ return this;
+ }
+
+ public Builder defaultActionRef(String name) {
+ target.defaultActionRef = name;
+ return this;
+ }
+
+ public Builder defaultClassRef( String defaultClassRef ) {
+ target.defaultClassRef = defaultClassRef;
+ return this;
+ }
+
+ /**
+ * sets the default Result type for this package
+ *
+ * @param defaultResultType
+ */
+ public Builder defaultResultType(String defaultResultType) {
+ target.defaultResultType = defaultResultType;
+ return this;
+ }
+
+ public Builder namespace(String namespace) {
+ if (namespace == null) {
+ target.namespace = "";
+ } else {
+ target.namespace = namespace;
+ }
+ return this;
+ }
+
+ public Builder needsRefresh(boolean needsRefresh) {
+ target.needsRefresh = needsRefresh;
+ return this;
+ }
+
+ public Builder addActionConfig(String name, ActionConfig action) {
+ target.actionConfigs.put(name, action);
+ return this;
+ }
+
+ public Builder addParents(List parents) {
+ for (PackageConfig config : parents) {
+ addParent(config);
+ }
+ return this;
+ }
+
+ public Builder addGlobalResultConfig(ResultConfig resultConfig) {
+ target.globalResultConfigs.put(resultConfig.getName(), resultConfig);
+ return this;
+ }
+
+ public Builder addGlobalResultConfigs(Map resultConfigs) {
+ target.globalResultConfigs.putAll(resultConfigs);
+ return this;
+ }
+
+ public Builder addExceptionMappingConfig(ExceptionMappingConfig exceptionMappingConfig) {
+ target.globalExceptionMappingConfigs.add(exceptionMappingConfig);
+ return this;
+ }
+
+ public Builder addGlobalExceptionMappingConfigs(List exceptionMappingConfigs) {
+ target.globalExceptionMappingConfigs.addAll(exceptionMappingConfigs);
+ return this;
+ }
+
+ public Builder addInterceptorConfig(InterceptorConfig config) {
+ target.interceptorConfigs.put(config.getName(), config);
+ return this;
+ }
+
+ public Builder addInterceptorStackConfig(InterceptorStackConfig config) {
+ target.interceptorConfigs.put(config.getName(), config);
+ return this;
+ }
+
+ public Builder addParent(PackageConfig parent) {
+ if (this.equals(parent)) {
+ LOG.error("A package cannot extend itself: " + target.name);
+ }
+
+ target.parents.add(0, parent);
+ return this;
+ }
+
+ public Builder addResultTypeConfig(ResultTypeConfig config) {
+ target.resultTypeConfigs.put(config.getName(), config);
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public boolean isNeedsRefresh() {
+ return target.needsRefresh;
+ }
+
+ public String getDefaultClassRef() {
+ return target.defaultClassRef;
+ }
+
+ public String getName() {
+ return target.name;
+ }
+
+ public String getNamespace() {
+ return target.namespace;
+ }
+
+ public String getFullDefaultResultType() {
+ return target.getFullDefaultResultType();
+ }
+
+ public ResultTypeConfig getResultType(String type) {
+ return target.getAllResultTypeConfigs().get(type);
+ }
+
+
+
+ public Object getInterceptorConfig(String name) {
+ return target.getAllInterceptorConfigs().get(name);
+ }
+
+ public PackageConfig build() {
+ target.actionConfigs = Collections.unmodifiableMap(target.actionConfigs);
+ target.globalResultConfigs = Collections.unmodifiableMap(target.globalResultConfigs);
+ target.interceptorConfigs = Collections.unmodifiableMap(target.interceptorConfigs);
+ target.resultTypeConfigs = Collections.unmodifiableMap(target.resultTypeConfigs);
+ target.globalExceptionMappingConfigs = Collections.unmodifiableList(target.globalExceptionMappingConfigs);
+ target.parents = Collections.unmodifiableList(target.parents);
+
+ PackageConfig result = target;
+ target = new PackageConfig(result);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "[BUILDER] "+target.toString();
+ }
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java
new file mode 100644
index 000000000..42b6cb3f6
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import java.util.Map;
+
+/**
+ *
+ *
+ * Actions implementing Parameterizable will receive a map of the static parameters defined in the action
+ * configuration.
+ *
+ *
The {@link com.opensymphony.xwork2.interceptor.StaticParametersInterceptor} must be in the action's interceptor
+ * queue for this to work.
+ *
+ *
+ *
+ * @author Jason Carreira
+ */
+public interface Parameterizable {
+
+ public void addParam(String name, String value);
+
+ void setParams(Map params);
+
+ Map getParams();
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java
new file mode 100644
index 000000000..70c91dbfd
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * Configuration for Result.
+ *
+ * In the xml configuration file this is defined as the result tag.
+ *
+ * @author Mike
+ */
+public class ResultConfig extends Located implements Serializable {
+
+ private Map params;
+ private String className;
+ private String name;
+
+
+ protected ResultConfig(String name, String className) {
+ this.name = name;
+ this.className = className;
+ params = new LinkedHashMap();
+ }
+
+ protected ResultConfig(ResultConfig orig) {
+ this.params = orig.params;
+ this.name = orig.name;
+ this.className = orig.className;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map getParams() {
+ return params;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ResultConfig)) {
+ return false;
+ }
+
+ final ResultConfig resultConfig = (ResultConfig) o;
+
+ if ((className != null) ? (!className.equals(resultConfig.className)) : (resultConfig.className != null)) {
+ return false;
+ }
+
+ if ((name != null) ? (!name.equals(resultConfig.name)) : (resultConfig.name != null)) {
+ return false;
+ }
+
+ if ((params != null) ? (!params.equals(resultConfig.params)) : (resultConfig.params != null)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = ((name != null) ? name.hashCode() : 0);
+ result = (29 * result) + ((className != null) ? className.hashCode() : 0);
+ result = (29 * result) + ((params != null) ? params.hashCode() : 0);
+
+ return result;
+ }
+
+ /**
+ * The builder for this object. An instance of this object is the only way to construct a new instance. The
+ * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
+ * After setting any values you need, call the {@link #build()} method to create the object.
+ */
+ public static final class Builder {
+ private ResultConfig target;
+
+ public Builder(String name, String className) {
+ target = new ResultConfig(name, className);
+ }
+
+ public Builder(ResultConfig orig) {
+ target = new ResultConfig(orig);
+ }
+
+ public Builder name(String name) {
+ target.name = name;
+ return this;
+ }
+
+ public Builder className(String name) {
+ target.className = name;
+ return this;
+ }
+
+ public Builder addParam(String name, String value) {
+ target.params.put(name, value);
+ return this;
+ }
+
+ public Builder addParams(Map params) {
+ target.params.putAll(params);
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public ResultConfig build() {
+ target.params = Collections.unmodifiableMap(target.params);
+ ResultConfig result = target;
+ target = new ResultConfig(target);
+ return result;
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java
new file mode 100644
index 000000000..a5c25dc89
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.config.entities;
+
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.Location;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * Configuration class for result types.
+ *
+ * In the xml configuration file this is defined as the result-type tag.
+ *
+ * @author Mike
+ * @author Rainer Hermanns
+ * @author Neo
+ */
+public class ResultTypeConfig extends Located implements Serializable {
+
+ private String className;
+ private String name;
+ private String defaultResultParam;
+
+ private Map params;
+
+ protected ResultTypeConfig(String name, String className) {
+ this.name = name;
+ this.className = className;
+ params = new LinkedHashMap