+*
+* @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:
+ *
+ *
+ *
+ * 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
+ *
+ * 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, falseotherwise.
+ */
+ 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, falseotherwise.
+ */
+ 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
+ *
+ * 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();
+ }
+
+ protected ResultTypeConfig(ResultTypeConfig orig) {
+ this.name = orig.name;
+ this.className = orig.className;
+ this.defaultResultParam = orig.defaultResultParam;
+ this.params = orig.params;
+ }
+
+
+ public void setDefaultResultParam(String defaultResultParam) {
+ this.defaultResultParam = defaultResultParam;
+ }
+
+ public String getDefaultResultParam() {
+ return this.defaultResultParam;
+ }
+
+ /**
+ * @deprecated Since 2.1, use {@link #getClassName()} instead
+ */
+ @Deprecated public String getClazz() {
+ return className;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map getParams() {
+ return this.params;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final ResultTypeConfig that = (ResultTypeConfig) o;
+
+ if (className != null ? !className.equals(that.className) : that.className != null) return false;
+ if (name != null ? !name.equals(that.name) : that.name != null) return false;
+ if (params != null ? !params.equals(that.params) : that.params != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = (className != null ? className.hashCode() : 0);
+ result = 29 * result + (name != null ? name.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 ResultTypeConfig target;
+
+ public Builder(String name, String className) {
+ target = new ResultTypeConfig(name, className);
+ }
+
+ public Builder(ResultTypeConfig orig) {
+ target = new ResultTypeConfig(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 defaultResultParam(String defaultResultParam) {
+ target.defaultResultParam = defaultResultParam;
+ return this;
+ }
+
+ public Builder location(Location loc) {
+ target.location = loc;
+ return this;
+ }
+
+ public ResultTypeConfig build() {
+ target.params = Collections.unmodifiableMap(target.params);
+ ResultTypeConfig result = target;
+ target = new ResultTypeConfig(target);
+ return result;
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java
new file mode 100644
index 000000000..8ab1843f1
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+public class UnknownHandlerConfig {
+ private String name;
+
+ public UnknownHandlerConfig(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/package.html
new file mode 100644
index 000000000..d05a35762
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/package.html
@@ -0,0 +1,18 @@
+
+
+
+Configuration entity classes. All objects ending in "Config" are immutable and must be constructed using
+their inner "Builder" class. For example, a PackageConfig object can be created via:
+
+
+ PackageConfig config = new PackageConfig.Builder("myPackage").build();
+
+
+ The methods on the builder object are chainable to support constructions like this:
+
+
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java
new file mode 100644
index 000000000..4ad23321f
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java
@@ -0,0 +1,278 @@
+/*
+ * $Id$
+ *
+ * Copyright 2003,2004 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.impl;
+
+import com.opensymphony.xwork2.util.PatternMatcher;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.*;
+
+import org.apache.commons.lang.math.NumberUtils;
+
+/**
+ *
Matches patterns against pre-compiled wildcard expressions pulled from
+ * target objects. It uses the wildcard matcher from the Apache Cocoon
+ * project. Patterns will be matched in the order they were added. The first
+ * match wins, so more specific patterns should be defined before less specific
+ * patterns.
+ *
+ * @since 2.1
+ */
+public abstract class AbstractMatcher implements Serializable {
+ /**
+ *
+ * Finds and precompiles the wildcard patterns. Patterns will be evaluated
+ * in the order they were added. Only patterns that actually contain a
+ * wildcard will be compiled.
+ *
+ *
+ *
+ * Patterns can optionally be matched "loosely". When the end of the pattern
+ * matches \*[^*]\*$ (wildcard, no wildcard, wildcard), if the pattern
+ * fails, it is also matched as if the last two characters didn't exist. The
+ * goal is to support the legacy "*!*" syntax, where the "!*" is optional.
+ *
+ *
+ * @param name The pattern
+ * @param target The object to associate with the pattern
+ * @param looseMatch
+ * To loosely match wildcards or not
+ */
+ public void addPattern(String name, E target, boolean looseMatch) {
+
+ Object pattern;
+
+ if (!wildcard.isLiteral(name)) {
+ if (looseMatch && (name.length() > 0) && (name.charAt(0) == '/')) {
+ name = name.substring(1);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Compiling pattern '" + name + "'");
+ }
+
+ pattern = wildcard.compilePattern(name);
+ compiledPatterns.add(new Mapping(name, pattern, target));
+
+ if (looseMatch) {
+ int lastStar = name.lastIndexOf('*');
+ if (lastStar > 1 && lastStar == name.length() - 1) {
+ if (name.charAt(lastStar - 1) != '*') {
+ pattern = wildcard.compilePattern(name.substring(0, lastStar - 1));
+ compiledPatterns.add(new Mapping(name, pattern, target));
+ }
+ }
+ }
+ }
+ }
+
+ public void freeze() {
+ compiledPatterns = Collections.unmodifiableList(new ArrayList>());
+ }
+
+ /**
+ *
Matches the path against the compiled wildcard patterns.
+ *
+ * @param potentialMatch The portion of the request URI for selecting a config.
+ * @return The action config if matched, else null
+ */
+ public E match(String potentialMatch) {
+ E config = null;
+
+ if (compiledPatterns.size() > 0) {
+ if (log.isDebugEnabled()) {
+ log.debug("Attempting to match '" + potentialMatch
+ + "' to a wildcard pattern, "+ compiledPatterns.size()
+ + " available");
+ }
+
+ Map vars = new LinkedHashMap();
+ for (Mapping m : compiledPatterns) {
+ if (wildcard.match(vars, potentialMatch, m.getPattern())) {
+ if (log.isDebugEnabled()) {
+ log.debug("Value matches pattern '"
+ + m.getOriginalPattern() + "'");
+ }
+
+ config =
+ convert(potentialMatch, m.getTarget(), vars);
+ break;
+ }
+ }
+ }
+
+ return config;
+ }
+
+ /**
+ *
Clones the target object and its children, replacing various
+ * properties with the values of the wildcard-matched strings.
+ *
+ * @param path The requested path
+ * @param orig The original object
+ * @param vars A Map of wildcard-matched strings
+ * @return A cloned object with appropriate properties replaced with
+ * wildcard-matched values
+ */
+ protected abstract E convert(String path, E orig, Map vars);
+
+ /**
+ *
Replaces parameter values
+ *
+ *
+ * @param orig The original parameters with placehold values
+ * @param vars A Map of wildcard-matched strings
+ */
+ protected Map replaceParameters(Map orig, Map vars) {
+ Map map = new LinkedHashMap();
+
+ //this will set the group index references, like {1}
+ for (String key : orig.keySet()) {
+ map.put(key, convertParam(orig.get(key), vars));
+ }
+
+ //the values map will contain entries like name->"Lex Luthor" and 1->"Lex Luthor"
+ //now add the non-numeric values
+ for (String key: vars.keySet()) {
+ if (!NumberUtils.isNumber(key)) {
+ map.put(key, vars.get(key));
+ }
+ }
+
+ return map;
+ }
+
+ /**
+ *
Inserts into a value wildcard-matched strings where specified
+ * with the {x} syntax. If a wildcard-matched value isn't found, the
+ * replacement token is turned into an empty string.
+ *
+ *
+ * @param val The value to convert
+ * @param vars A Map of wildcard-matched strings
+ * @return The new value
+ */
+ protected String convertParam(String val, Map vars) {
+ if (val == null) {
+ return null;
+ }
+
+ int len = val.length();
+ StringBuilder ret = new StringBuilder();
+ char c;
+ String varVal;
+ for (int x=0; x Stores a compiled wildcard pattern and the object it came
+ * from.
+ *
+ * @return The associated object
+ */
+ public E getTarget() {
+ return this.config;
+ }
+
+ /**
+ *
Gets the original wildcard pattern.
+ *
+ * @return The original pattern
+ */
+ public String getOriginalPattern() {
+ return this.original;
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java
new file mode 100644
index 000000000..3058b032c
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java
@@ -0,0 +1,148 @@
+/*
+ * $Id$
+ *
+ * Copyright 2003,2004 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.impl;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.util.PatternMatcher;
+import com.opensymphony.xwork2.util.WildcardHelper;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
Matches paths against pre-compiled wildcard expressions pulled from
+ * action configs. It uses the wildcard matcher from the Apache Cocoon
+ * project. Patterns will be matched in the order they exist in the
+ * config file. The first match wins, so more specific patterns should be
+ * defined before less specific patterns.
+ */
+public class ActionConfigMatcher extends AbstractMatcher implements Serializable {
+
+ /**
+ *
Finds and precompiles the wildcard patterns from the ActionConfig
+ * "path" attributes. ActionConfig's will be evaluated in the order they
+ * exist in the config file. Only paths that actually contain a
+ * wildcard will be compiled. Patterns will matched strictly.
+ *
+ * @param configs An array of ActionConfig's to process
+ * @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead
+ */
+ @Deprecated public ActionConfigMatcher(Map configs) {
+ this(configs, false);
+ }
+
+ /**
+ *
Finds and precompiles the wildcard patterns from the ActionConfig
+ * "path" attributes. ActionConfig's will be evaluated in the order they
+ * exist in the config file. Only paths that actually contain a
+ * wildcard will be compiled.
+ *
+ *
Patterns can optionally be matched "loosely". When
+ * the end of the pattern matches \*[^*]\*$ (wildcard, no wildcard,
+ * wildcard), if the pattern fails, it is also matched as if the
+ * last two characters didn't exist. The goal is to support the
+ * legacy "*!*" syntax, where the "!*" is optional.
+ *
+ * @param configs An array of ActionConfig's to process
+ * @param looseMatch To loosely match wildcards or not
+ * @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead
+ */
+ @Deprecated public ActionConfigMatcher(Map configs,
+ boolean looseMatch) {
+
+ this(new WildcardHelper(), configs, looseMatch);
+ }
+
+ /**
+ *
Finds and precompiles the wildcard patterns from the ActionConfig
+ * "path" attributes. ActionConfig's will be evaluated in the order they
+ * exist in the config file. Only paths that actually contain a
+ * wildcard will be compiled.
+ *
+ *
Patterns can optionally be matched "loosely". When
+ * the end of the pattern matches \*[^*]\*$ (wildcard, no wildcard,
+ * wildcard), if the pattern fails, it is also matched as if the
+ * last two characters didn't exist. The goal is to support the
+ * legacy "*!*" syntax, where the "!*" is optional.
+ *
+ * @param configs An array of ActionConfig's to process
+ * @param looseMatch To loosely match wildcards or not
+ */
+ public ActionConfigMatcher(PatternMatcher> patternMatcher,
+ Map configs,
+ boolean looseMatch) {
+ super(patternMatcher);
+ for (String name : configs.keySet()) {
+ addPattern(name, configs.get(name), looseMatch);
+ }
+ }
+
+ /**
+ *
Clones the ActionConfig and its children, replacing various
+ * properties with the values of the wildcard-matched strings.
+ *
+ * @param path The requested path
+ * @param orig The original ActionConfig
+ * @param vars A Map of wildcard-matched strings
+ * @return A cloned ActionConfig with appropriate properties replaced with
+ * wildcard-matched values
+ */
+ @Override public ActionConfig convert(String path, ActionConfig orig,
+ Map vars) {
+
+ String className = convertParam(orig.getClassName(), vars);
+ String methodName = convertParam(orig.getMethodName(), vars);
+ String pkgName = convertParam(orig.getPackageName(), vars);
+
+ Map params = replaceParameters(orig.getParams(), vars);
+
+ Map results = new LinkedHashMap();
+ for (String name : orig.getResults().keySet()) {
+ ResultConfig result = orig.getResults().get(name);
+ name = convertParam(name, vars);
+ ResultConfig r = new ResultConfig.Builder(name, convertParam(result.getClassName(), vars))
+ .addParams(replaceParameters(result.getParams(), vars))
+ .build();
+ results.put(name, r);
+ }
+
+ List exs = new ArrayList();
+ for (ExceptionMappingConfig ex : orig.getExceptionMappings()) {
+ String name = convertParam(ex.getName(), vars);
+ String exClassName = convertParam(ex.getExceptionClassName(), vars);
+ String exResult = convertParam(ex.getResult(), vars);
+ Map exParams = replaceParameters(ex.getParams(), vars);
+ ExceptionMappingConfig e = new ExceptionMappingConfig.Builder(name, exClassName, exResult).addParams(exParams).build();
+ exs.add(e);
+ }
+
+ return new ActionConfig.Builder(pkgName, orig.getName(), className)
+ .methodName(methodName)
+ .addParams(params)
+ .addResultConfigs(results)
+ .addInterceptors(orig.getInterceptors())
+ .addExceptionMappings(exs)
+ .location(orig.getLocation())
+ .build();
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
new file mode 100644
index 000000000..f77da0aa6
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
@@ -0,0 +1,484 @@
+/*
+ * 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.impl;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.DefaultTextProvider;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.TextProvider;
+import com.opensymphony.xwork2.config.*;
+import com.opensymphony.xwork2.config.entities.*;
+import com.opensymphony.xwork2.config.providers.InterceptorBuilder;
+import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
+import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
+import com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.inject.*;
+import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
+import com.opensymphony.xwork2.ognl.OgnlUtil;
+import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
+import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.PatternMatcher;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import ognl.PropertyAccessor;
+
+import java.util.*;
+
+
+/**
+ * DefaultConfiguration
+ *
+ * @author Jason Carreira
+ * Created Feb 24, 2003 7:38:06 AM
+ */
+public class DefaultConfiguration implements Configuration {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(DefaultConfiguration.class);
+
+
+ // Programmatic Action Configurations
+ protected Map packageContexts = new LinkedHashMap();
+ protected RuntimeConfiguration runtimeConfiguration;
+ protected Container container;
+ protected String defaultFrameworkBeanName;
+ protected Set loadedFileNames = new TreeSet();
+ protected List unknownHandlerStack;
+
+
+ ObjectFactory objectFactory;
+
+ public DefaultConfiguration() {
+ this("xwork");
+ }
+
+ public DefaultConfiguration(String defaultBeanName) {
+ this.defaultFrameworkBeanName = defaultBeanName;
+ }
+
+
+ public PackageConfig getPackageConfig(String name) {
+ return packageContexts.get(name);
+ }
+
+ public List getUnknownHandlerStack() {
+ return unknownHandlerStack;
+ }
+
+ public void setUnknownHandlerStack(List unknownHandlerStack) {
+ this.unknownHandlerStack = unknownHandlerStack;
+ }
+
+ public Set getPackageConfigNames() {
+ return packageContexts.keySet();
+ }
+
+ public Map getPackageConfigs() {
+ return packageContexts;
+ }
+
+ public Set getLoadedFileNames() {
+ return loadedFileNames;
+ }
+
+ public RuntimeConfiguration getRuntimeConfiguration() {
+ return runtimeConfiguration;
+ }
+
+ /**
+ * @return the container
+ */
+ public Container getContainer() {
+ return container;
+ }
+
+ public void addPackageConfig(String name, PackageConfig packageContext) {
+ PackageConfig check = packageContexts.get(name);
+ if (check != null) {
+ if (check.getLocation() != null && packageContext.getLocation() != null
+ && check.getLocation().equals(packageContext.getLocation())) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("The package name '" + name
+ + "' is already been loaded by the same location and could be removed: "
+ + packageContext.getLocation());
+ }
+ } else {
+ throw new ConfigurationException("The package name '" + name
+ + "' at location "+packageContext.getLocation()
+ + " is already been used by another package at location " + check.getLocation(),
+ packageContext);
+ }
+ }
+ packageContexts.put(name, packageContext);
+ }
+
+ public PackageConfig removePackageConfig(String packageName) {
+ return packageContexts.remove(packageName);
+ }
+
+ /**
+ * Allows the configuration to clean up any resources used
+ */
+ public void destroy() {
+ packageContexts.clear();
+ loadedFileNames.clear();
+ }
+
+ public void rebuildRuntimeConfiguration() {
+ runtimeConfiguration = buildRuntimeConfiguration();
+ }
+
+ /**
+ * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
+ * buildRuntimeConfiguration().
+ *
+ * @throws ConfigurationException
+ */
+ public synchronized void reload(List providers) throws ConfigurationException {
+
+ // Silly copy necessary due to lack of ability to cast generic lists
+ List contProviders = new ArrayList();
+ contProviders.addAll(providers);
+
+ reloadContainer(contProviders);
+ }
+
+ /**
+ * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
+ * buildRuntimeConfiguration().
+ *
+ * @throws ConfigurationException
+ */
+ public synchronized List reloadContainer(List providers) throws ConfigurationException {
+ packageContexts.clear();
+ loadedFileNames.clear();
+ List packageProviders = new ArrayList();
+
+ ContainerProperties props = new ContainerProperties();
+ ContainerBuilder builder = new ContainerBuilder();
+ for (final ContainerProvider containerProvider : providers)
+ {
+ containerProvider.init(this);
+ containerProvider.register(builder, props);
+ }
+ props.setConstants(builder);
+
+ builder.factory(Configuration.class, new Factory() {
+ public Configuration create(Context context) throws Exception {
+ return DefaultConfiguration.this;
+ }
+ });
+
+ ActionContext oldContext = ActionContext.getContext();
+ try {
+ // Set the bootstrap container for the purposes of factory creation
+ Container bootstrap = createBootstrapContainer();
+ setContext(bootstrap);
+ container = builder.create(false);
+ setContext(container);
+ objectFactory = container.getInstance(ObjectFactory.class);
+
+ // Process the configuration providers first
+ for (final ContainerProvider containerProvider : providers)
+ {
+ if (containerProvider instanceof PackageProvider) {
+ container.inject(containerProvider);
+ ((PackageProvider)containerProvider).loadPackages();
+ packageProviders.add((PackageProvider)containerProvider);
+ }
+ }
+
+ // Then process any package providers from the plugins
+ Set packageProviderNames = container.getInstanceNames(PackageProvider.class);
+ if (packageProviderNames != null) {
+ for (String name : packageProviderNames) {
+ PackageProvider provider = container.getInstance(PackageProvider.class, name);
+ provider.init(this);
+ provider.loadPackages();
+ packageProviders.add(provider);
+ }
+ }
+
+ rebuildRuntimeConfiguration();
+ } finally {
+ if (oldContext == null) {
+ ActionContext.setContext(null);
+ }
+ }
+ return packageProviders;
+ }
+
+ protected ActionContext setContext(Container cont) {
+ ActionContext context = ActionContext.getContext();
+ if (context == null) {
+ ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack();
+ context = new ActionContext(vs.getContext());
+ ActionContext.setContext(context);
+ }
+ return context;
+ }
+
+ protected Container createBootstrapContainer() {
+ ContainerBuilder builder = new ContainerBuilder();
+ builder.factory(ObjectFactory.class, Scope.SINGLETON);
+ builder.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON);
+ builder.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON);
+ builder.factory(XWorkConverter.class, Scope.SINGLETON);
+ builder.factory(XWorkBasicConverter.class, Scope.SINGLETON);
+ builder.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON);
+ builder.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON);
+ builder.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON);
+ builder.factory(OgnlUtil.class, Scope.SINGLETON);
+ builder.constant("devMode", "false");
+ builder.constant("logMissingProperties", "false");
+ return builder.create(true);
+ }
+
+ /**
+ * This builds the internal runtime configuration used by Xwork for finding and configuring Actions from the
+ * programmatic configuration data structures. All of the old runtime configuration will be discarded and rebuilt.
+ *
+ *
+ * It basically flattens the data structures to make the information easier to access. It will take
+ * an {@link ActionConfig} and combine its data with all inherited dast. For example, if the {@link ActionConfig}
+ * is in a package that contains a global result and it also contains a result, the resulting {@link ActionConfig}
+ * will have two results.
+ */
+ protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException {
+ Map> namespaceActionConfigs = new LinkedHashMap>();
+ Map namespaceConfigs = new LinkedHashMap();
+
+ for (PackageConfig packageConfig : packageContexts.values()) {
+
+ if (!packageConfig.isAbstract()) {
+ String namespace = packageConfig.getNamespace();
+ Map configs = namespaceActionConfigs.get(namespace);
+
+ if (configs == null) {
+ configs = new LinkedHashMap();
+ }
+
+ Map actionConfigs = packageConfig.getAllActionConfigs();
+
+ for (Object o : actionConfigs.keySet()) {
+ String actionName = (String) o;
+ ActionConfig baseConfig = actionConfigs.get(actionName);
+ configs.put(actionName, buildFullActionConfig(packageConfig, baseConfig));
+ }
+
+
+
+ namespaceActionConfigs.put(namespace, configs);
+ if (packageConfig.getFullDefaultActionRef() != null) {
+ namespaceConfigs.put(namespace, packageConfig.getFullDefaultActionRef());
+ }
+ }
+ }
+
+ return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs);
+ }
+
+ private void setDefaultResults(Map results, PackageConfig packageContext) {
+ String defaultResult = packageContext.getFullDefaultResultType();
+
+ for (Map.Entry entry : results.entrySet()) {
+
+ if (entry.getValue() == null) {
+ ResultTypeConfig resultTypeConfig = packageContext.getAllResultTypeConfigs().get(defaultResult);
+ entry.setValue(new ResultConfig.Builder(null, resultTypeConfig.getClassName()).build());
+ }
+ }
+ }
+
+ /**
+ * Builds the full runtime actionconfig with all of the defaults and inheritance
+ *
+ * @param packageContext the PackageConfig which holds the base config we're building from
+ * @param baseConfig the ActionConfig which holds only the configuration specific to itself, without the defaults
+ * and inheritance
+ * @return a full ActionConfig for runtime configuration with all of the inherited and default params
+ * @throws com.opensymphony.xwork2.config.ConfigurationException
+ *
+ */
+ private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionConfig baseConfig) throws ConfigurationException {
+ Map params = new TreeMap(baseConfig.getParams());
+ Map results = new TreeMap();
+
+ if (!baseConfig.getPackageName().equals(packageContext.getName()) && packageContexts.containsKey(baseConfig.getPackageName())) {
+ results.putAll(packageContexts.get(baseConfig.getPackageName()).getAllGlobalResults());
+ } else {
+ results.putAll(packageContext.getAllGlobalResults());
+ }
+
+ results.putAll(baseConfig.getResults());
+
+ setDefaultResults(results, packageContext);
+
+ List interceptors = new ArrayList(baseConfig.getInterceptors());
+
+ if (interceptors.size() <= 0) {
+ String defaultInterceptorRefName = packageContext.getFullDefaultInterceptorRef();
+
+ if (defaultInterceptorRefName != null) {
+ interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName,
+ new LinkedHashMap(), packageContext.getLocation(), objectFactory));
+ }
+ }
+
+
+
+ return new ActionConfig.Builder(baseConfig)
+ .addParams(params)
+ .addResultConfigs(results)
+ .defaultClassName(packageContext.getDefaultClassRef()) // fill in default if non class has been provided
+ .interceptors(interceptors)
+ .addExceptionMappings(packageContext.getAllExceptionMappingConfigs())
+ .build();
+ }
+
+
+ private class RuntimeConfigurationImpl implements RuntimeConfiguration {
+ private Map> namespaceActionConfigs;
+ private Map namespaceActionConfigMatchers;
+ private NamespaceMatcher namespaceMatcher;
+ private Map namespaceConfigs;
+
+ public RuntimeConfigurationImpl(Map> namespaceActionConfigs, Map namespaceConfigs) {
+ this.namespaceActionConfigs = namespaceActionConfigs;
+ this.namespaceConfigs = namespaceConfigs;
+
+ PatternMatcher matcher = container.getInstance(PatternMatcher.class);
+
+ this.namespaceActionConfigMatchers = new LinkedHashMap();
+ this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet());
+
+ for (String ns : namespaceActionConfigs.keySet()) {
+ namespaceActionConfigMatchers.put(ns,
+ new ActionConfigMatcher(matcher,
+ namespaceActionConfigs.get(ns), true));
+ }
+ }
+
+
+ /**
+ * Gets the configuration information for an action name, or returns null if the
+ * name is not recognized.
+ *
+ * @param name the name of the action
+ * @param namespace the namespace for the action or null for the empty namespace, ""
+ * @return the configuration information for action requested
+ */
+ public synchronized ActionConfig getActionConfig(String namespace, String name) {
+ ActionConfig config = findActionConfigInNamespace(namespace, name);
+
+ // try wildcarded namespaces
+ if (config == null) {
+ NamespaceMatch match = namespaceMatcher.match(namespace);
+ if (match != null) {
+ config = findActionConfigInNamespace(match.getPattern(), name);
+
+ // If config found, place all the matches found in the namespace processing in the action's parameters
+ if (config != null) {
+ config = new ActionConfig.Builder(config)
+ .addParams(match.getVariables())
+ .build();
+ }
+ }
+ }
+
+ // fail over to empty namespace
+ if ((config == null) && (namespace != null) && (!"".equals(namespace.trim()))) {
+ config = findActionConfigInNamespace("", name);
+ }
+
+
+ return config;
+ }
+
+ ActionConfig findActionConfigInNamespace(String namespace, String name) {
+ ActionConfig config = null;
+ if (namespace == null) {
+ namespace = "";
+ }
+ Map actions = namespaceActionConfigs.get(namespace);
+ if (actions != null) {
+ config = actions.get(name);
+ // Check wildcards
+ if (config == null) {
+ config = namespaceActionConfigMatchers.get(namespace).match(name);
+ // fail over to default action
+ if (config == null) {
+ String defaultActionRef = namespaceConfigs.get(namespace);
+ if (defaultActionRef != null) {
+ config = actions.get(defaultActionRef);
+ }
+ }
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Gets the configuration settings for every action.
+ *
+ * @return a Map of namespace - > Map of ActionConfig objects, with the key being the action name
+ */
+ public synchronized Map> getActionConfigs() {
+ return namespaceActionConfigs;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buff = new StringBuilder("RuntimeConfiguration - actions are\n");
+
+ for (String namespace : namespaceActionConfigs.keySet()) {
+ Map actionConfigs = namespaceActionConfigs.get(namespace);
+
+ for (String s : actionConfigs.keySet()) {
+ buff.append(namespace).append("/").append(s).append("\n");
+ }
+ }
+
+ return buff.toString();
+ }
+ }
+
+ class ContainerProperties extends LocatableProperties {
+ private static final long serialVersionUID = -7320625750836896089L;
+
+ @Override
+ public Object setProperty(String key, String value) {
+ String oldValue = getProperty(key);
+ if (oldValue != null && !oldValue.equals(value) && !defaultFrameworkBeanName.equals(oldValue)) {
+ LOG.info("Overriding property "+key+" - old value: "+oldValue+" new value: "+value);
+ }
+ return super.setProperty(key, value);
+ }
+
+ public void setConstants(ContainerBuilder builder) {
+ for (Object keyobj : keySet()) {
+ String key = (String)keyobj;
+ builder.factory(String.class, key,
+ new LocatableConstantFactory(getProperty(key), getPropertyLocation(key)));
+ }
+ }
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/LocatableConstantFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/LocatableConstantFactory.java
new file mode 100644
index 000000000..eb218f859
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/LocatableConstantFactory.java
@@ -0,0 +1,34 @@
+/**
+ *
+ */
+package com.opensymphony.xwork2.config.impl;
+
+import com.opensymphony.xwork2.inject.Context;
+import com.opensymphony.xwork2.inject.Factory;
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.LocationUtils;
+
+/**
+ * Factory that remembers where a constant came from
+ */
+public class LocatableConstantFactory extends Located implements Factory {
+ T constant;
+ public LocatableConstantFactory(T constant, Object location) {
+ this.constant = constant;
+ setLocation(LocationUtils.getLocation(location));
+ }
+
+ public T create(Context ignored) {
+ return constant;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ sb.append(" defined at ");
+ sb.append(getLocation().toString());
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/LocatableFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/LocatableFactory.java
new file mode 100644
index 000000000..2f95dfe70
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/LocatableFactory.java
@@ -0,0 +1,52 @@
+package com.opensymphony.xwork2.config.impl;
+
+import com.opensymphony.xwork2.inject.Context;
+import com.opensymphony.xwork2.inject.Factory;
+import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.util.location.Located;
+import com.opensymphony.xwork2.util.location.LocationUtils;
+
+import java.util.LinkedHashMap;
+
+/**
+ * Attaches location information to the factory.
+ */
+public class LocatableFactory extends Located implements Factory {
+
+
+ private Class implementation;
+ private Class type;
+ private String name;
+ private Scope scope;
+
+ public LocatableFactory(String name, Class type, Class implementation, Scope scope, Object location) {
+ this.implementation = implementation;
+ this.type = type;
+ this.name = name;
+ this.scope = scope;
+ setLocation(LocationUtils.getLocation(location));
+ }
+
+ @SuppressWarnings("unchecked")
+ public T create(Context context) {
+ Object obj = context.getContainer().inject(implementation);
+ return (T) obj;
+ }
+
+ @Override
+ public String toString() {
+ String fields = new LinkedHashMap() {
+ {
+ put("type", type);
+ put("name", name);
+ put("implementation", implementation);
+ put("scope", scope);
+ }
+ }.toString();
+ StringBuilder sb = new StringBuilder(fields);
+ sb.append(super.toString());
+ sb.append(" defined at ");
+ sb.append(getLocation().toString());
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/MockConfiguration.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/MockConfiguration.java
new file mode 100644
index 000000000..9059c2bb3
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/MockConfiguration.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2003,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.impl;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.config.ContainerProvider;
+import com.opensymphony.xwork2.config.PackageProvider;
+import com.opensymphony.xwork2.config.RuntimeConfiguration;
+import com.opensymphony.xwork2.config.entities.PackageConfig;
+import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
+import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Simple configuration used for unit testing
+ */
+public class MockConfiguration implements Configuration {
+
+ private Map packages = new HashMap();
+ private Set loadedFiles = new HashSet();
+ private Container container;
+ protected List unknownHandlerStack;
+ private ContainerBuilder builder;
+
+ public MockConfiguration() {
+ builder = new ContainerBuilder();
+ }
+
+ public void selfRegister() {
+ //this cannot be done in the constructor, as it causes an infinite loop
+ builder.factory(Configuration.class, MockConfiguration.class, Scope.SINGLETON);
+ LocatableProperties props = new LocatableProperties();
+ new XWorkConfigurationProvider().register(builder, props);
+ builder.constant("devMode", "false");
+ container = builder.create(true);
+ }
+
+ public PackageConfig getPackageConfig(String name) {
+ return packages.get(name);
+ }
+
+ public Set getPackageConfigNames() {
+ return packages.keySet();
+ }
+
+ public Map getPackageConfigs() {
+ return packages;
+ }
+
+ public RuntimeConfiguration getRuntimeConfiguration() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void addPackageConfig(String name, PackageConfig packageContext) {
+ packages.put(name, packageContext);
+ }
+
+ public void buildRuntimeConfiguration() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void destroy() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void rebuildRuntimeConfiguration() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void reload(List providers) throws ConfigurationException {
+ throw new UnsupportedOperationException();
+ }
+
+ public PackageConfig removePackageConfig(String name) {
+ return packages.remove(name);
+ }
+
+ public Container getContainer() {
+ return container;
+ }
+
+ public Set getLoadedFileNames() {
+ return loadedFiles;
+ }
+
+ public List reloadContainer(
+ List containerProviders)
+ throws ConfigurationException {
+ throw new UnsupportedOperationException();
+ }
+
+ public List getUnknownHandlerStack() {
+ return unknownHandlerStack;
+ }
+
+ public void setUnknownHandlerStack(List unknownHandlerStack) {
+ this.unknownHandlerStack = unknownHandlerStack;
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/NamespaceMatch.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/NamespaceMatch.java
new file mode 100644
index 000000000..52a08867a
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/NamespaceMatch.java
@@ -0,0 +1,47 @@
+/*
+ * 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.impl;
+
+import java.util.Map;
+
+/**
+ * Represents a match from a namespace pattern matching.
+ *
+ * @Since 2.1
+ */
+public class NamespaceMatch {
+ private String pattern;
+ private Map variables;
+
+ public NamespaceMatch(String pattern, Map variables) {
+ this.pattern = pattern;
+ this.variables = variables;
+ }
+
+ /**
+ * @return The pattern that was matched
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * @return The variables containing the matched values
+ */
+ public Map getVariables() {
+ return variables;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/NamespaceMatcher.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/NamespaceMatcher.java
new file mode 100644
index 000000000..3d64c3370
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/NamespaceMatcher.java
@@ -0,0 +1,51 @@
+/*
+ * 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.impl;
+
+import com.opensymphony.xwork2.util.PatternMatcher;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Matches namespace strings against a wildcard pattern matcher
+ *
+ * @Since 2.1
+ */
+public class NamespaceMatcher extends AbstractMatcher {
+ public NamespaceMatcher(PatternMatcher> patternMatcher,
+ Set namespaces) {
+ super(patternMatcher);
+ for (String name : namespaces) {
+ if (!patternMatcher.isLiteral(name)) {
+ addPattern(name, new NamespaceMatch(name, null), false);
+ }
+ }
+ }
+
+ @Override
+ protected NamespaceMatch convert(String path, NamespaceMatch orig, Map vars) {
+ /*Map origVars = (Map)vars;
+ Map map = new HashMap();
+ for (Map.Entry entry : origVars.entrySet()) {
+ if (entry.getKey().length() == 1) {
+ map.put("ns"+entry.getKey(), entry.getValue());
+ }
+ }
+ */
+ return new NamespaceMatch(orig.getPattern(), vars);
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/package.html
new file mode 100644
index 000000000..cdfed5f83
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/package.html
@@ -0,0 +1 @@
+Configuration implementation classes.
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/config/package.html
new file mode 100644
index 000000000..a3de692bc
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/package.html
@@ -0,0 +1 @@
+Configuration core classes.
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java
new file mode 100644
index 000000000..102f08ef7
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java
@@ -0,0 +1,215 @@
+/*
+ * 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.providers;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.InterceptorConfig;
+import com.opensymphony.xwork2.config.entities.InterceptorLocator;
+import com.opensymphony.xwork2.config.entities.InterceptorMapping;
+import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import com.opensymphony.xwork2.util.location.Location;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Builds a list of interceptors referenced by the refName in the supplied PackageConfig.
+ *
+ * @author Mike
+ * @author Rainer Hermanns
+ * @author tmjee
+ * @version $Date$ $Id$
+ */
+public class InterceptorBuilder {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InterceptorBuilder.class);
+
+
+ /**
+ * Builds a list of interceptors referenced by the refName in the supplied PackageConfig (InterceptorMapping object).
+ *
+ * @param interceptorLocator
+ * @param refName
+ * @param refParams
+ * @return list of interceptors referenced by the refName in the supplied PackageConfig (InterceptorMapping object).
+ * @throws ConfigurationException
+ */
+ public static List constructInterceptorReference(InterceptorLocator interceptorLocator,
+ String refName, Map refParams, Location location, ObjectFactory objectFactory) throws ConfigurationException {
+ Object referencedConfig = interceptorLocator.getInterceptorConfig(refName);
+ List result = new ArrayList();
+
+ if (referencedConfig == null) {
+ throw new ConfigurationException("Unable to find interceptor class referenced by ref-name " + refName, location);
+ } else {
+ if (referencedConfig instanceof InterceptorConfig) {
+ InterceptorConfig config = (InterceptorConfig) referencedConfig;
+ Interceptor inter = null;
+ try {
+
+ inter = objectFactory.buildInterceptor(config, refParams);
+ result.add(new InterceptorMapping(refName, inter));
+ } catch (ConfigurationException ex) {
+ LOG.warn("Unable to load config class " + config.getClassName() + " at " +
+ ex.getLocation() + " probably due to a missing jar, which might " +
+ "be fine if you never plan to use the " + config.getName() + " interceptor");
+ LOG.error("Actual exception", ex);
+ }
+
+ } else if (referencedConfig instanceof InterceptorStackConfig) {
+ InterceptorStackConfig stackConfig = (InterceptorStackConfig) referencedConfig;
+
+ if ((refParams != null) && (refParams.size() > 0)) {
+ result = constructParameterizedInterceptorReferences(interceptorLocator, stackConfig, refParams, objectFactory);
+ } else {
+ result.addAll(stackConfig.getInterceptors());
+ }
+
+ } else {
+ LOG.error("Got unexpected type for interceptor " + refName + ". Got " + referencedConfig);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Builds a list of interceptors referenced by the refName in the supplied PackageConfig overriding the properties
+ * of the referenced interceptor with refParams.
+ *
+ * @param interceptorLocator
+ * @param stackConfig
+ * @param refParams The overridden interceptor properies
+ * @return list of interceptors referenced by the refName in the supplied PackageConfig overridden with refParams.
+ */
+ private static List constructParameterizedInterceptorReferences(
+ InterceptorLocator interceptorLocator, InterceptorStackConfig stackConfig, Map refParams,
+ ObjectFactory objectFactory) {
+ List result;
+ Map> params = new LinkedHashMap>();
+
+ /*
+ * We strip
+ *
+ *
+ * someValue
+ * anotherValue
+ *
+ *
+ * down to map
+ * interceptor1 -> [param1 -> someValue, param2 -> anotherValue]
+ *
+ * or
+ *
+ * someValue
+ * anotherValue
+ *
+ *
+ * down to map
+ * interceptorStack1 -> [interceptor1.param1 -> someValue, interceptor1.param2 -> anotherValue]
+ *
+ */
+ for (String key : refParams.keySet()) {
+ String value = refParams.get(key);
+
+ try {
+ String name = key.substring(0, key.indexOf('.'));
+ key = key.substring(key.indexOf('.') + 1);
+
+ Map map;
+ if (params.containsKey(name)) {
+ map = params.get(name);
+ } else {
+ map = new LinkedHashMap();
+ }
+
+ map.put(key, value);
+ params.put(name, map);
+
+ } catch (Exception e) {
+ LOG.warn("No interceptor found for name = " + key);
+ }
+ }
+
+ result = new ArrayList(stackConfig.getInterceptors());
+
+ for (String key : params.keySet()) {
+
+ Map map = params.get(key);
+
+
+ Object interceptorCfgObj = interceptorLocator.getInterceptorConfig(key);
+
+ /*
+ * Now we attempt to separate out param that refers to Interceptor
+ * and Interceptor stack, eg.
+ *
+ *
+ * someValue
+ * ...
+ *
+ *
+ * vs
+ *
+ *
+ * someValue
+ * ...
+ *
+ */
+ if (interceptorCfgObj instanceof InterceptorConfig) { // interceptor-ref param refer to an interceptor
+ InterceptorConfig cfg = (InterceptorConfig) interceptorCfgObj;
+ Interceptor interceptor = objectFactory.buildInterceptor(cfg, map);
+
+ InterceptorMapping mapping = new InterceptorMapping(key, interceptor);
+ if (result != null && result.contains(mapping)) {
+ // if an existing interceptor mapping exists,
+ // we remove from the result Set, just to make sure
+ // there's always one unique mapping.
+ int index = result.indexOf(mapping);
+ result.set(index, mapping);
+ } else {
+ result.add(mapping);
+ }
+ } else
+ if (interceptorCfgObj instanceof InterceptorStackConfig) { // interceptor-ref param refer to an interceptor stack
+
+ // If its an interceptor-stack, we call this method recursively untill,
+ // all the params (eg. interceptorStack1.interceptor1.param etc.)
+ // are resolved down to a specific interceptor.
+
+ InterceptorStackConfig stackCfg = (InterceptorStackConfig) interceptorCfgObj;
+ List tmpResult = constructParameterizedInterceptorReferences(interceptorLocator, stackCfg, map, objectFactory);
+ for (InterceptorMapping tmpInterceptorMapping : tmpResult) {
+ if (result.contains(tmpInterceptorMapping)) {
+ int index = result.indexOf(tmpInterceptorMapping);
+ result.set(index, tmpInterceptorMapping);
+ } else {
+ result.add(tmpInterceptorMapping);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
new file mode 100644
index 000000000..bd3539860
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
@@ -0,0 +1,120 @@
+package com.opensymphony.xwork2.config.providers;
+
+import com.opensymphony.xwork2.ActionProxyFactory;
+import com.opensymphony.xwork2.DefaultActionProxyFactory;
+import com.opensymphony.xwork2.DefaultTextProvider;
+import com.opensymphony.xwork2.DefaultUnknownHandlerManager;
+import com.opensymphony.xwork2.TextProvider;
+import com.opensymphony.xwork2.TextProviderSupport;
+import com.opensymphony.xwork2.UnknownHandlerManager;
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.conversion.NullHandler;
+import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
+import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
+import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
+import com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.ognl.ObjectProxy;
+import com.opensymphony.xwork2.ognl.OgnlReflectionContextFactory;
+import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
+import com.opensymphony.xwork2.ognl.OgnlUtil;
+import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
+import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
+import com.opensymphony.xwork2.ognl.accessor.ObjectAccessor;
+import com.opensymphony.xwork2.ognl.accessor.ObjectProxyPropertyAccessor;
+import com.opensymphony.xwork2.ognl.accessor.XWorkCollectionPropertyAccessor;
+import com.opensymphony.xwork2.ognl.accessor.XWorkEnumerationAccessor;
+import com.opensymphony.xwork2.ognl.accessor.XWorkIteratorPropertyAccessor;
+import com.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessor;
+import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
+import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.PatternMatcher;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.WildcardHelper;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import com.opensymphony.xwork2.validator.ActionValidatorManager;
+import com.opensymphony.xwork2.validator.AnnotationActionValidatorManager;
+import com.opensymphony.xwork2.validator.DefaultActionValidatorManager;
+import com.opensymphony.xwork2.validator.DefaultValidatorFactory;
+import com.opensymphony.xwork2.validator.DefaultValidatorFileParser;
+import com.opensymphony.xwork2.validator.ValidatorFactory;
+import com.opensymphony.xwork2.validator.ValidatorFileParser;
+import ognl.MethodAccessor;
+import ognl.PropertyAccessor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class XWorkConfigurationProvider implements ConfigurationProvider {
+
+ public void destroy() {
+ }
+
+ public void init(Configuration configuration) throws ConfigurationException {
+ }
+
+ public void loadPackages() throws ConfigurationException {
+ }
+
+ public boolean needsReload() {
+ return false;
+ }
+
+ public void register(ContainerBuilder builder, LocatableProperties props)
+ throws ConfigurationException {
+
+ builder.factory(com.opensymphony.xwork2.ObjectFactory.class)
+ .factory(ActionProxyFactory.class, DefaultActionProxyFactory.class, Scope.SINGLETON)
+ .factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
+ .factory(XWorkConverter.class, Scope.SINGLETON)
+ .factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON)
+ .factory(ValidatorFactory.class, DefaultValidatorFactory.class, Scope.SINGLETON)
+ .factory(ValidatorFileParser.class, DefaultValidatorFileParser.class, Scope.SINGLETON)
+ .factory(PatternMatcher.class, WildcardHelper.class, Scope.SINGLETON)
+ .factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON)
+ .factory(ReflectionContextFactory.class, OgnlReflectionContextFactory.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, Object.class.getName(), ObjectAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, Iterator.class.getName(), XWorkIteratorPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, Enumeration.class.getName(), XWorkEnumerationAccessor.class, Scope.SINGLETON)
+ .factory(UnknownHandlerManager.class, DefaultUnknownHandlerManager.class, Scope.SINGLETON)
+
+ // silly workarounds for ognl since there is no way to flush its caches
+ .factory(PropertyAccessor.class, List.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, ArrayList.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, HashSet.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, Set.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, HashMap.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, Map.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON)
+
+ .factory(PropertyAccessor.class, Collection.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
+ .factory(PropertyAccessor.class, ObjectProxy.class.getName(), ObjectProxyPropertyAccessor.class, Scope.SINGLETON)
+ .factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON)
+ .factory(MethodAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
+ .factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON)
+ .factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON)
+ .factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
+ .factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON)
+ .factory(TextProvider.class, TextProviderSupport.class, Scope.SINGLETON)
+ .factory(OgnlUtil.class, Scope.SINGLETON)
+ .factory(XWorkBasicConverter.class, Scope.SINGLETON);
+ props.setProperty("devMode", Boolean.FALSE.toString());
+ props.setProperty("logMissingProperties", Boolean.FALSE.toString());
+ props.setProperty("enableOGNLExpressionCache", Boolean.TRUE.toString());
+ }
+
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
new file mode 100644
index 000000000..a35faebf8
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
@@ -0,0 +1,1004 @@
+/*
+ * 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.providers;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.config.ConfigurationUtil;
+import com.opensymphony.xwork2.config.entities.*;
+import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
+import com.opensymphony.xwork2.config.impl.LocatableFactory;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.util.*;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+import com.opensymphony.xwork2.util.location.Location;
+import com.opensymphony.xwork2.util.location.LocationUtils;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.util.*;
+
+
+/**
+ * Looks in the classpath for an XML file, "xwork.xml" by default,
+ * and uses it for the XWork configuration.
+ *
+ * @author tmjee
+ * @author Rainer Hermanns
+ * @author Neo
+ * @version $Revision$
+ */
+public class XmlConfigurationProvider implements ConfigurationProvider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlConfigurationProvider.class);
+
+ private List documents;
+ private Set includedFileNames;
+ private String configFileName;
+ private ObjectFactory objectFactory;
+
+ private Set loadedFileUrls = new HashSet();
+ private boolean errorIfMissing;
+ private Map dtdMappings;
+ private Configuration configuration;
+ private boolean throwExceptionOnDuplicateBeans = true;
+
+ public XmlConfigurationProvider() {
+ this("xwork.xml", true);
+ }
+
+ public XmlConfigurationProvider(String filename) {
+ this(filename, true);
+ }
+
+ public XmlConfigurationProvider(String filename, boolean errorIfMissing) {
+ this.configFileName = filename;
+ this.errorIfMissing = errorIfMissing;
+
+ Map mappings = new HashMap();
+ mappings.put("-//OpenSymphony Group//XWork 2.1.3//EN", "xwork-2.1.3.dtd");
+ mappings.put("-//OpenSymphony Group//XWork 2.1//EN", "xwork-2.1.dtd");
+ mappings.put("-//OpenSymphony Group//XWork 2.0//EN", "xwork-2.0.dtd");
+ mappings.put("-//OpenSymphony Group//XWork 1.1.1//EN", "xwork-1.1.1.dtd");
+ mappings.put("-//OpenSymphony Group//XWork 1.1//EN", "xwork-1.1.dtd");
+ mappings.put("-//OpenSymphony Group//XWork 1.0//EN", "xwork-1.0.dtd");
+ setDtdMappings(mappings);
+ }
+
+ public void setThrowExceptionOnDuplicateBeans(boolean val) {
+ this.throwExceptionOnDuplicateBeans = val;
+ }
+
+ public void setDtdMappings(Map mappings) {
+ this.dtdMappings = Collections.unmodifiableMap(mappings);
+ }
+
+ @Inject
+ public void setObjectFactory(ObjectFactory objectFactory) {
+ this.objectFactory = objectFactory;
+ }
+
+ /**
+ * Returns an unmodifiable map of DTD mappings
+ */
+ public Map getDtdMappings() {
+ return dtdMappings;
+ }
+
+ public void init(Configuration configuration) {
+ this.configuration = configuration;
+ this.includedFileNames = configuration.getLoadedFileNames();
+ loadDocuments(configFileName);
+ }
+
+ public void destroy() {
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof XmlConfigurationProvider)) {
+ return false;
+ }
+
+ final XmlConfigurationProvider xmlConfigurationProvider = (XmlConfigurationProvider) o;
+
+ if ((configFileName != null) ? (!configFileName.equals(xmlConfigurationProvider.configFileName)) : (xmlConfigurationProvider.configFileName != null)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((configFileName != null) ? configFileName.hashCode() : 0);
+ }
+
+ private void loadDocuments(String configFileName) {
+ try {
+ loadedFileUrls.clear();
+ documents = loadConfigurationFiles(configFileName, null);
+ } catch (ConfigurationException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ConfigurationException("Error loading configuration file " + configFileName, e);
+ }
+ }
+
+ public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
+ LOG.info("Parsing configuration file [" + configFileName + "]");
+ Map loadedBeans = new HashMap();
+ for (Document doc : documents) {
+ Element rootElement = doc.getDocumentElement();
+ NodeList children = rootElement.getChildNodes();
+ int childSize = children.getLength();
+
+ for (int i = 0; i < childSize; i++) {
+ Node childNode = children.item(i);
+
+ if (childNode instanceof Element) {
+ Element child = (Element) childNode;
+
+ final String nodeName = child.getNodeName();
+
+ if ("bean".equals(nodeName)) {
+ String type = child.getAttribute("type");
+ String name = child.getAttribute("name");
+ String impl = child.getAttribute("class");
+ String onlyStatic = child.getAttribute("static");
+ String scopeStr = child.getAttribute("scope");
+ boolean optional = "true".equals(child.getAttribute("optional"));
+ Scope scope = Scope.SINGLETON;
+ if ("default".equals(scopeStr)) {
+ scope = Scope.DEFAULT;
+ } else if ("request".equals(scopeStr)) {
+ scope = Scope.REQUEST;
+ } else if ("session".equals(scopeStr)) {
+ scope = Scope.SESSION;
+ } else if ("singleton".equals(scopeStr)) {
+ scope = Scope.SINGLETON;
+ } else if ("thread".equals(scopeStr)) {
+ scope = Scope.THREAD;
+ }
+
+ if (StringUtils.isEmpty(name)) {
+ name = Container.DEFAULT_NAME;
+ }
+
+ try {
+ Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
+ Class ctype = cimpl;
+ if (StringUtils.isNotEmpty(type)) {
+ ctype = ClassLoaderUtil.loadClass(type, getClass());
+ }
+ if ("true".equals(onlyStatic)) {
+ // Force loading of class to detect no class def found exceptions
+ cimpl.getDeclaredClasses();
+ containerBuilder.injectStatics(cimpl);
+ } else {
+ if (containerBuilder.contains(ctype, name)) {
+ Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name));
+ if (throwExceptionOnDuplicateBeans) {
+ throw new ConfigurationException("Bean type " + ctype + " with the name " +
+ name + " has already been loaded by " + loc, child);
+ }
+ }
+
+ // Force loading of class to detect no class def found exceptions
+ cimpl.getDeclaredConstructors();
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl);
+ }
+ containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
+ }
+ loadedBeans.put(ctype.getName() + name, child);
+ } catch (Throwable ex) {
+ if (!optional) {
+ throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
+ } else {
+ LOG.debug("Unable to load optional class: " + ex);
+ }
+ }
+ } else if ("constant".equals(nodeName)) {
+ String name = child.getAttribute("name");
+ String value = child.getAttribute("value");
+ props.setProperty(name, value, childNode);
+ } else if (nodeName.equals("unknown-handler-stack")) {
+ List unknownHandlerStack = new ArrayList();
+ NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
+ int unknownHandlersSize = unknownHandlers.getLength();
+
+ for (int k = 0; k < unknownHandlersSize; k++) {
+ Element unknownHandler = (Element) unknownHandlers.item(k);
+ unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name")));
+ }
+
+ if (!unknownHandlerStack.isEmpty())
+ configuration.setUnknownHandlerStack(unknownHandlerStack);
+ }
+ }
+ }
+ }
+ }
+
+ public void loadPackages() throws ConfigurationException {
+ List reloads = new ArrayList();
+ for (Document doc : documents) {
+ Element rootElement = doc.getDocumentElement();
+ NodeList children = rootElement.getChildNodes();
+ int childSize = children.getLength();
+
+ for (int i = 0; i < childSize; i++) {
+ Node childNode = children.item(i);
+
+ if (childNode instanceof Element) {
+ Element child = (Element) childNode;
+
+ final String nodeName = child.getNodeName();
+
+ if ("package".equals(nodeName)) {
+ PackageConfig cfg = addPackage(child);
+ if (cfg.isNeedsRefresh()) {
+ reloads.add(child);
+ }
+ }
+ }
+ }
+ loadExtraConfiguration(doc);
+ }
+
+ if (reloads.size() > 0) {
+ reloadRequiredPackages(reloads);
+ }
+
+ for (Document doc : documents) {
+ loadExtraConfiguration(doc);
+ }
+
+ documents.clear();
+ configuration = null;
+ }
+
+ private void reloadRequiredPackages(List reloads) {
+ if (reloads.size() > 0) {
+ List result = new ArrayList();
+ for (Element pkg : reloads) {
+ PackageConfig cfg = addPackage(pkg);
+ if (cfg.isNeedsRefresh()) {
+ result.add(pkg);
+ }
+ }
+ if ((result.size() > 0) && (result.size() != reloads.size())) {
+ reloadRequiredPackages(result);
+ return;
+ }
+
+ // Print out error messages for all misconfigured inheritence packages
+ if (result.size() > 0) {
+ for (Element rp : result) {
+ String parent = rp.getAttribute("extends");
+ if (parent != null) {
+ List parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
+ if (parents != null && parents.size() <= 0) {
+ LOG.error("Unable to find parent packages " + parent);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Tells whether the ConfigurationProvider should reload its configuration. This method should only be called
+ * if ConfigurationManager.isReloadingConfigs() is true.
+ *
+ * @return true if the file has been changed since the last time we read it
+ */
+ public boolean needsReload() {
+
+ for (String url : loadedFileUrls) {
+ if (FileManager.fileNeedsReloading(url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException {
+ String name = actionElement.getAttribute("name");
+ String className = actionElement.getAttribute("class");
+ String methodName = actionElement.getAttribute("method");
+ Location location = DomHelper.getLocationObject(actionElement);
+
+ if (location == null) {
+ LOG.warn("location null for " + className);
+ }
+ //methodName should be null if it's not set
+ methodName = (methodName.trim().length() > 0) ? methodName.trim() : null;
+
+ // if there isnt a class name specified for an then try to
+ // use the default-class-ref from the
+ if (StringUtils.isEmpty(className)) {
+ // if there is a package default-class-ref use that, otherwise use action support
+ /* if (StringUtils.isNotEmpty(packageContext.getDefaultClassRef())) {
+ className = packageContext.getDefaultClassRef();
+ } else {
+ className = ActionSupport.class.getName();
+ }*/
+
+ } else {
+ if (!verifyAction(className, name, location)) {
+ if (LOG.isErrorEnabled())
+ LOG.error("Unable to verify action [#0] with class [#1], from [#2]", name, className, location.toString());
+ return;
+ }
+ }
+
+
+
+ Map results;
+ try {
+ results = buildResults(actionElement, packageContext);
+ } catch (ConfigurationException e) {
+ throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement);
+ }
+
+ List interceptorList = buildInterceptorList(actionElement, packageContext);
+
+ List exceptionMappings = buildExceptionMappings(actionElement, packageContext);
+
+ ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className)
+ .methodName(methodName)
+ .addResultConfigs(results)
+ .addInterceptors(interceptorList)
+ .addExceptionMappings(exceptionMappings)
+ .addParams(XmlHelper.getParams(actionElement))
+ .location(location)
+ .build();
+ packageContext.addActionConfig(name, actionConfig);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Loaded " + (StringUtils.isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "") + name + " in '" + packageContext.getName() + "' package:" + actionConfig);
+ }
+ }
+
+ protected boolean verifyAction(String className, String name, Location loc) {
+ if (className.indexOf('{') > -1) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Action class [" + className + "] contains a wildcard " +
+ "replacement value, so it can't be verified");
+ }
+ return true;
+ }
+ try {
+ if (objectFactory.isNoArgConstructorRequired()) {
+ Class clazz = objectFactory.getClassInstance(className);
+ if (!Modifier.isPublic(clazz.getModifiers())) {
+ throw new ConfigurationException("Action class [" + className + "] is not public", loc);
+ }
+ clazz.getConstructor(new Class[]{});
+ }
+ } catch (ClassNotFoundException e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Class not found for action [" + className + "]", e);
+ }
+ throw new ConfigurationException("Action class [" + className + "] not found", loc);
+ } catch (NoSuchMethodException e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("No constructor found for action [" + className + "]", e);
+ }
+ throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc);
+ } catch (RuntimeException ex) {
+ // Probably not a big deal, like request or session-scoped Spring 2 beans that need a real request
+ LOG.info("Unable to verify action class [" + className + "] exists at initialization");
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Action verification cause", ex);
+ }
+ } catch (Exception ex) {
+ // Default to failing fast
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Unable to verify action class [" + className + "]", ex);
+ }
+ throw new ConfigurationException(ex, loc);
+ }
+ return true;
+ }
+
+ /**
+ * Create a PackageConfig from an XML element representing it.
+ */
+ protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
+ PackageConfig.Builder newPackage = buildPackageContext(packageElement);
+
+ if (newPackage.isNeedsRefresh()) {
+ return newPackage.build();
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Loaded " + newPackage);
+ }
+
+ // add result types (and default result) to this package
+ addResultTypes(newPackage, packageElement);
+
+ // load the interceptors and interceptor stacks for this package
+ loadInterceptors(newPackage, packageElement);
+
+ // load the default interceptor reference for this package
+ loadDefaultInterceptorRef(newPackage, packageElement);
+
+ // load the default class ref for this package
+ loadDefaultClassRef(newPackage, packageElement);
+
+ // load the global result list for this package
+ loadGlobalResults(newPackage, packageElement);
+
+ // load the global exception handler list for this package
+ loadGobalExceptionMappings(newPackage, packageElement);
+
+ // get actions
+ NodeList actionList = packageElement.getElementsByTagName("action");
+
+ for (int i = 0; i < actionList.getLength(); i++) {
+ Element actionElement = (Element) actionList.item(i);
+ addAction(actionElement, newPackage);
+ }
+
+ // load the default action reference for this package
+ loadDefaultActionRef(newPackage, packageElement);
+
+ PackageConfig cfg = newPackage.build();
+ configuration.addPackageConfig(cfg.getName(), cfg);
+ return cfg;
+ }
+
+ protected void addResultTypes(PackageConfig.Builder packageContext, Element element) {
+ NodeList resultTypeList = element.getElementsByTagName("result-type");
+
+ for (int i = 0; i < resultTypeList.getLength(); i++) {
+ Element resultTypeElement = (Element) resultTypeList.item(i);
+ String name = resultTypeElement.getAttribute("name");
+ String className = resultTypeElement.getAttribute("class");
+ String def = resultTypeElement.getAttribute("default");
+
+ Location loc = DomHelper.getLocationObject(resultTypeElement);
+
+ Class clazz = verifyResultType(className, loc);
+ if (clazz != null) {
+ String paramName = null;
+ try {
+ paramName = (String) clazz.getField("DEFAULT_PARAM").get(null);
+ }
+ catch (Throwable t) {
+ // if we get here, the result type doesn't have a default param defined.
+ }
+ ResultTypeConfig.Builder resultType = new ResultTypeConfig.Builder(name, className).defaultResultParam(paramName)
+ .location(DomHelper.getLocationObject(resultTypeElement));
+
+ Map params = XmlHelper.getParams(resultTypeElement);
+
+ if (!params.isEmpty()) {
+ resultType.addParams(params);
+ }
+ packageContext.addResultTypeConfig(resultType.build());
+
+ // set the default result type
+ if ("true".equals(def)) {
+ packageContext.defaultResultType(name);
+ }
+ }
+ }
+ }
+
+ protected Class verifyResultType(String className, Location loc) {
+ try {
+ return objectFactory.getClassInstance(className);
+ } catch (ClassNotFoundException e) {
+ LOG.warn("Result class [" + className + "] doesn't exist (ClassNotFoundException) at " +
+ loc.toString() + ", ignoring", e);
+ } catch (NoClassDefFoundError e) {
+ LOG.warn("Result class [" + className + "] doesn't exist (NoClassDefFoundError) at " +
+ loc.toString() + ", ignoring", e);
+ }
+
+ return null;
+ }
+
+ protected List buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException {
+ List interceptorList = new ArrayList();
+ NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref");
+
+ for (int i = 0; i < interceptorRefList.getLength(); i++) {
+ Element interceptorRefElement = (Element) interceptorRefList.item(i);
+
+ if (interceptorRefElement.getParentNode().equals(element) || interceptorRefElement.getParentNode().getNodeName().equals(element.getNodeName())) {
+ List interceptors = lookupInterceptorReference(context, interceptorRefElement);
+ interceptorList.addAll(interceptors);
+ }
+ }
+
+ return interceptorList;
+ }
+
+ /**
+ * This method builds a package context by looking for the parents of this new package.
+ *
+ * If no parents are found, it will return a root package.
+ */
+ protected PackageConfig.Builder buildPackageContext(Element packageElement) {
+ String parent = packageElement.getAttribute("extends");
+ String abstractVal = packageElement.getAttribute("abstract");
+ boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue();
+ String name = StringUtils.defaultString(packageElement.getAttribute("name"));
+ String namespace = StringUtils.defaultString(packageElement.getAttribute("namespace"));
+
+
+ if (StringUtils.isNotEmpty(packageElement.getAttribute("externalReferenceResolver"))) {
+ throw new ConfigurationException("The 'externalReferenceResolver' attribute has been removed. Please use " +
+ "a custom ObjectFactory or Interceptor.", packageElement);
+ }
+
+ PackageConfig.Builder cfg = new PackageConfig.Builder(name)
+ .namespace(namespace)
+ .isAbstract(isAbstract)
+ .location(DomHelper.getLocationObject(packageElement));
+
+
+ if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { // has parents, let's look it up
+
+ List parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
+
+ if (parents.size() <= 0) {
+ cfg.needsRefresh(true);
+ } else {
+ cfg.addParents(parents);
+ }
+ }
+
+ return cfg;
+ }
+
+ /**
+ * Build a map of ResultConfig objects from below a given XML element.
+ */
+ protected Map buildResults(Element element, PackageConfig.Builder packageContext) {
+ NodeList resultEls = element.getElementsByTagName("result");
+
+ Map results = new LinkedHashMap();
+
+ for (int i = 0; i < resultEls.getLength(); i++) {
+ Element resultElement = (Element) resultEls.item(i);
+
+ if (resultElement.getParentNode().equals(element) || resultElement.getParentNode().getNodeName().equals(element.getNodeName())) {
+ String resultName = resultElement.getAttribute("name");
+ String resultType = resultElement.getAttribute("type");
+
+ // if you don't specify a name on , it defaults to "success"
+ if (StringUtils.isEmpty(resultName)) {
+ resultName = Action.SUCCESS;
+ }
+
+ // there is no result type, so let's inherit from the parent package
+ if (StringUtils.isEmpty(resultType)) {
+ resultType = packageContext.getFullDefaultResultType();
+
+ // now check if there is a result type now
+ if (StringUtils.isEmpty(resultType)) {
+ // uh-oh, we have a problem
+ throw new ConfigurationException("No result type specified for result named '"
+ + resultName + "', perhaps the parent package does not specify the result type?", resultElement);
+ }
+ }
+
+
+ ResultTypeConfig config = packageContext.getResultType(resultType);
+
+ if (config == null) {
+ throw new ConfigurationException("There is no result type defined for type '" + resultType
+ + "' mapped with name '" + resultName + "'."
+ + " Did you mean '" + guessResultType(resultType) + "'?", resultElement);
+ }
+
+ String resultClass = config.getClazz();
+
+ // invalid result type specified in result definition
+ if (resultClass == null) {
+ throw new ConfigurationException("Result type '" + resultType + "' is invalid");
+ }
+
+ Map resultParams = XmlHelper.getParams(resultElement);
+
+ if (resultParams.size() == 0) // maybe we just have a body - therefore a default parameter
+ {
+ // if something then we add a parameter of 'something' as this is the most used result param
+ if (resultElement.getChildNodes().getLength() >= 1) {
+ resultParams = new LinkedHashMap();
+
+ String paramName = config.getDefaultResultParam();
+ if (paramName != null) {
+ StringBuilder paramValue = new StringBuilder();
+ for (int j = 0; j < resultElement.getChildNodes().getLength(); j++) {
+ if (resultElement.getChildNodes().item(j).getNodeType() == Node.TEXT_NODE) {
+ String val = resultElement.getChildNodes().item(j).getNodeValue();
+ if (val != null) {
+ paramValue.append(val);
+ }
+ }
+ }
+ String val = paramValue.toString().trim();
+ if (val.length() > 0) {
+ resultParams.put(paramName, val);
+ }
+ } else {
+ LOG.warn("no default parameter defined for result of type " + config.getName());
+ }
+ }
+ }
+
+ // create new param map, so that the result param can override the config param
+ Map params = new LinkedHashMap();
+ Map configParams = config.getParams();
+ if (configParams != null) {
+ params.putAll(configParams);
+ }
+ params.putAll(resultParams);
+
+ ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass)
+ .addParams(params)
+ .location(DomHelper.getLocationObject(element))
+ .build();
+ results.put(resultConfig.getName(), resultConfig);
+ }
+ }
+
+ return results;
+ }
+
+ protected String guessResultType(String type) {
+ StringBuilder sb = null;
+ if (type != null) {
+ sb = new StringBuilder();
+ boolean capNext = false;
+ for (int x=0; x buildExceptionMappings(Element element, PackageConfig.Builder packageContext) {
+ NodeList exceptionMappingEls = element.getElementsByTagName("exception-mapping");
+
+ List exceptionMappings = new ArrayList();
+
+ for (int i = 0; i < exceptionMappingEls.getLength(); i++) {
+ Element ehElement = (Element) exceptionMappingEls.item(i);
+
+ if (ehElement.getParentNode().equals(element) || ehElement.getParentNode().getNodeName().equals(element.getNodeName())) {
+ String emName = ehElement.getAttribute("name");
+ String exceptionClassName = ehElement.getAttribute("exception");
+ String exceptionResult = ehElement.getAttribute("result");
+
+ Map params = XmlHelper.getParams(ehElement);
+
+ if (StringUtils.isEmpty(emName)) {
+ emName = exceptionResult;
+ }
+
+ ExceptionMappingConfig ehConfig = new ExceptionMappingConfig.Builder(emName, exceptionClassName, exceptionResult)
+ .addParams(params)
+ .location(DomHelper.getLocationObject(ehElement))
+ .build();
+ exceptionMappings.add(ehConfig);
+ }
+ }
+
+ return exceptionMappings;
+ }
+
+
+ protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) {
+ NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref");
+
+ if (resultTypeList.getLength() > 0) {
+ Element defaultRefElement = (Element) resultTypeList.item(0);
+ packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name"));
+ }
+ }
+
+ protected void loadDefaultActionRef(PackageConfig.Builder packageContext, Element element) {
+ NodeList resultTypeList = element.getElementsByTagName("default-action-ref");
+
+ if (resultTypeList.getLength() > 0) {
+ Element defaultRefElement = (Element) resultTypeList.item(0);
+ packageContext.defaultActionRef(defaultRefElement.getAttribute("name"));
+ }
+ }
+
+ /**
+ * Load all of the global results for this package from the XML element.
+ */
+ protected void loadGlobalResults(PackageConfig.Builder packageContext, Element packageElement) {
+ NodeList globalResultList = packageElement.getElementsByTagName("global-results");
+
+ if (globalResultList.getLength() > 0) {
+ Element globalResultElement = (Element) globalResultList.item(0);
+ Map results = buildResults(globalResultElement, packageContext);
+ packageContext.addGlobalResultConfigs(results);
+ }
+ }
+
+ protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) {
+ NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref");
+ if (defaultClassRefList.getLength() > 0) {
+ Element defaultClassRefElement = (Element) defaultClassRefList.item(0);
+ packageContext.defaultClassRef(defaultClassRefElement.getAttribute("class"));
+ }
+ }
+
+ /**
+ * Load all of the global results for this package from the XML element.
+ */
+ protected void loadGobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) {
+ NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings");
+
+ if (globalExceptionMappingList.getLength() > 0) {
+ Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0);
+ List