http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java b/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java new file mode 100644 index 0000000..859ccfd --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java @@ -0,0 +1,288 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; + + +/** +* <!-- START SNIPPET: description --> +* +* This result invokes an entire other action, complete with it's own interceptor stack and result. +* +* <!-- END SNIPPET: description --> +* +* <b>This result type takes the following parameters:</b> +* +* <!-- START SNIPPET: params --> +* +* <ul> +* +* <li><b>actionName (default)</b> - the name of the action that will be chained to</li> +* +* <li><b>namespace</b> - used to determine which namespace the Action is in that we're chaining. If namespace is null, +* this defaults to the current namespace</li> +* +* <li><b>method</b> - used to specify another method on target action to be invoked. +* If null, this defaults to execute method</li> +* +* <li><b>skipActions</b> - (optional) the list of comma separated action names for the +* actions that could be chained to</li> +* +* </ul> +* +* <!-- END SNIPPET: params --> +* +* <b>Example:</b> +* +* <pre><!-- START SNIPPET: example --> +* <package name="public" extends="struts-default"> +* <!-- Chain creatAccount to login, using the default parameter --> +* <action name="createAccount" class="..."> +* <result type="chain">login</result> +* </action> +* +* <action name="login" class="..."> +* <!-- Chain to another namespace --> +* <result type="chain"> +* <param name="actionName">dashboard</param> +* <param name="namespace">/secure</param> +* </result> +* </action> +* </package> +* +* <package name="secure" extends="struts-default" namespace="/secure"> +* <action name="dashboard" class="..."> +* <result>dashboard.jsp</result> +* </action> +* </package> +* <!-- END SNIPPET: example --></pre> +* +* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a> +*/ +public class ActionChainResult implements Result { + + private static final Logger LOG = LogManager.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 <code>namespace/action!method</code> keys. + */ + public static LinkedList<String> getChainHistory() { + LinkedList<String> chainHistory = (LinkedList<String>) 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<String, Object> extraContext = new HashMap<>(); + extraContext.put(ActionContext.VALUE_STACK, ActionContext.getContext().getValueStack()); + extraContext.put(ActionContext.PARAMETERS, ActionContext.getContext().getParameters()); + extraContext.put(CHAIN_HISTORY, ActionChainResult.getChainHistory()); + + 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<String> 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<String> 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; + } +}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java new file mode 100644 index 0000000..60ff183 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java @@ -0,0 +1,353 @@ +/* + * 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. <p> + * <p/> + * 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: + * <p/> + * <ul><code>ActionContext context = ActionContext.getContext();</code></ul> + * <p/> + * 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> actionContext = new ThreadLocal<>(); + + /** + * 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"; + + private Map<String, Object> context; + + /** + * Creates a new ActionContext initialized with another context. + * + * @param context a context map. + */ + public ActionContext(Map<String, Object> 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<String, Object> 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<String, Object> getApplication() { + return (Map<String, Object>) 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 <tt>null</tt>. + */ + public static ActionContext getContext() { + return actionContext.get(); + } + + /** + * Sets the action's context map. + * + * @param contextMap the context map. + */ + public void setContextMap(Map<String, Object> contextMap) { + getContext().context = contextMap; + } + + /** + * Gets the context map. + * + * @return the context map. + */ + public Map<String, Object> 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<String, Object> 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<String, Object> getConversionErrors() { + Map<String, Object> 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<String, Object> 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<String, Object> getParameters() { + return (Map<String, Object>) get(PARAMETERS); + } + + /** + * Sets a map of action session values. + * + * @param session the session values. + */ + public void setSession(Map<String, Object> 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<String, Object> getSession() { + return (Map<String, Object>) 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> T getInstance(Class<T> 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 <tt>null</tt> 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); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java new file mode 100644 index 0000000..58c992a --- /dev/null +++ b/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); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java new file mode 100644 index 0000000..d94516b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java @@ -0,0 +1,179 @@ +/* + * 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 <code>invoke()</code> 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 <tt>true</tt> 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 <code>ActionChainResult</code>s 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. + * <p/> + * The "intended" purpose of this method is to allow PreResultListeners to + * override the result code returned by the Action. + * <p/> + * 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. + * <p/> + * 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. + * <p/> + * 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. + * <p/> + * The ActionInvocation implementation must guarantee that listeners will be called in + * the order in which they are registered. + * <p/> + * 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. + * <p/> + * 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 <tt>true</tt>, 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). + * <p/> + * 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) ; + + /** + * Prepares instance of ActionInvocation to be serializable, + * which simple means removing all unserializable fields, eg. Container + * + * @return ActionInvocation which can be serialize (eg. into HttpSession) + */ + ActionInvocation serialize(); + + /** + * Performs opposite process to restore back ActionInvocation after deserialisation + * + * @param actionContext current {@link ActionContext} + * @return fully operational ActionInvocation + */ + ActionInvocation deserialize(ActionContext actionContext); + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java new file mode 100644 index 0000000..95110ac --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java @@ -0,0 +1,103 @@ +/* + * 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. + * <p/> + * 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 <tt>true</tt> 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 <tt>null</tt> if no method has been specified (meaning <code>execute</code> will be invoked). + * + * @return the method to execute + */ + String getMethod(); + + /** + * Gets status of the method value's initialization. + * + * @return true if the method returned by getMethod() is not a default initializer value. + */ + boolean isMethodSpecified(); + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java b/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java new file mode 100644 index 0000000..5cf4de0 --- /dev/null +++ b/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. + * <p/> + * 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. + * <p/> + * <b>Note:</b> This is the most used create method. + * + * @param namespace the namespace of the action, can be <tt>null</tt> + * @param actionName the name of the action + * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt> + * @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<String, Object> 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. + * <p/> + * <b>Note:</b> This is the most used create method. + * + * @param namespace the namespace of the action, can be <tt>null</tt> + * @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 <tt>null</tt> + * @return ActionProxy the created action proxy + * @since 2.1.1 + */ + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> 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 <tt>null</tt> + * @param actionName the name of the action + * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt> + * @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<String, Object> 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 <tt>null</tt> + * @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 <tt>null</tt> + * @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<String, Object> 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 <tt>null</tt> + * @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); + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java new file mode 100644 index 0000000..fd8675b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java @@ -0,0 +1,302 @@ +/* + * 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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.Serializable; +import java.util.*; + + +/** + * 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 = LogManager.getLogger(ActionSupport.class); + + private final ValidationAwareSupport validationAware = new ValidationAwareSupport(); + + private transient TextProvider textProvider; + private Container container; + + public void setActionErrors(Collection<String> errorMessages) { + validationAware.setActionErrors(errorMessages); + } + + public Collection<String> getActionErrors() { + return validationAware.getActionErrors(); + } + + public void setActionMessages(Collection<String> messages) { + validationAware.setActionMessages(messages); + } + + public Collection<String> getActionMessages() { + return validationAware.getActionMessages(); + } + + /** + * @deprecated Use {@link #getActionErrors()}. + */ + @Deprecated + public Collection<String> getErrorMessages() { + return getActionErrors(); + } + + /** + * @deprecated Use {@link #getFieldErrors()}. + */ + @Deprecated + public Map<String, List<String>> getErrors() { + return getFieldErrors(); + } + + public void setFieldErrors(Map<String, List<String>> errorMap) { + validationAware.setFieldErrors(errorMap); + } + + public Map<String, List<String>> getFieldErrors() { + return validationAware.getFieldErrors(); + } + + public Locale getLocale() { + ActionContext ctx = ActionContext.getContext(); + if (ctx != null) { + return ctx.getLocale(); + } else { + LOG.debug("Action context not initialized"); + return null; + } + } + + public boolean hasKey(String key) { + return getTextProvider().hasKey(key); + } + + public String getText(String aTextName) { + return getTextProvider().getText(aTextName); + } + + public String getText(String aTextName, String defaultValue) { + return getTextProvider().getText(aTextName, defaultValue); + } + + public String getText(String aTextName, String defaultValue, String obj) { + return getTextProvider().getText(aTextName, defaultValue, obj); + } + + public String getText(String aTextName, List<?> args) { + return getTextProvider().getText(aTextName, args); + } + + public String getText(String key, String[] args) { + return getTextProvider().getText(key, args); + } + + public String getText(String aTextName, String defaultValue, List<?> args) { + return getTextProvider().getText(aTextName, defaultValue, args); + } + + public String getText(String key, String defaultValue, String[] args) { + return getTextProvider().getText(key, defaultValue, args); + } + + public String getText(String key, String defaultValue, List<?> args, ValueStack stack) { + return getTextProvider().getText(key, defaultValue, args, stack); + } + + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + return getTextProvider().getText(key, defaultValue, args, stack); + } + + /** + * Dedicated method to support I10N and conversion errors + * + * @param key message which contains formatting string + * @param expr that should be formatted + * @return formatted expr with format specified by key + */ + public String getFormatted(String key, String expr) { + Map<String, Object> conversionErrors = ActionContext.getContext().getConversionErrors(); + if (conversionErrors.containsKey(expr)) { + String[] vals = (String[]) conversionErrors.get(expr); + return vals[0]; + } else { + final ValueStack valueStack = ActionContext.getContext().getValueStack(); + final Object val = valueStack.findValue(expr); + return getText(key, Arrays.asList(val)); + } + } + + public ResourceBundle getTexts() { + return getTextProvider().getTexts(); + } + + public ResourceBundle getTexts(String aBundleName) { + return getTextProvider().getTexts(aBundleName); + } + + public void addActionError(String anErrorMessage) { + validationAware.addActionError(anErrorMessage); + } + + public void addActionMessage(String aMessage) { + validationAware.addActionMessage(aMessage); + } + + public void addFieldError(String fieldName, String errorMessage) { + validationAware.addFieldError(fieldName, errorMessage); + } + + public String input() throws Exception { + return INPUT; + } + + public String doDefault() throws Exception { + return SUCCESS; + } + + /** + * A default implementation that does nothing an returns "success". + * <p/> + * Subclasses should override this method to provide their business logic. + * <p/> + * See also {@link com.opensymphony.xwork2.Action#execute()}. + * + * @return returns {@link #SUCCESS} + * @throws Exception can be thrown by subclasses. + */ + public String execute() throws Exception { + return SUCCESS; + } + + public boolean hasActionErrors() { + return validationAware.hasActionErrors(); + } + + public boolean hasActionMessages() { + return validationAware.hasActionMessages(); + } + + public boolean hasErrors() { + return validationAware.hasErrors(); + } + + public boolean hasFieldErrors() { + return validationAware.hasFieldErrors(); + } + + /** + * Clears field errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearFieldErrors() { + validationAware.clearFieldErrors(); + } + + /** + * Clears action errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearActionErrors() { + validationAware.clearActionErrors(); + } + + /** + * Clears messages. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearMessages() { + validationAware.clearMessages(); + } + + /** + * Clears all errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearErrors() { + validationAware.clearErrors(); + } + + /** + * Clears all errors and messages. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearErrorsAndMessages() { + validationAware.clearErrorsAndMessages(); + } + + /** + * A default implementation that validates nothing. + * Subclasses should override this method to provide validations. + */ + public void validate() { + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * <!-- START SNIPPET: pause-method --> + * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return + * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc. + * <p/> + * <p/> + * The next time this action is invoked (and using the same continuation ID), the method will resume immediately + * after where this method was called, with the entire call stack in the execute method restored. + * <p/> + * <p/> + * Note: this method can <b>only</b> be called within the {@link #execute()} method. + * <!-- END SNIPPET: pause-method --> + * + * @param result the result to return - the same type of return value in the {@link #execute()} method. + */ + public void pause(String result) { + } + + /** + * If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory}, + * inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in, + * then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field + * for future references and at the returns reference to that field + * + * @return reference to field with TextProvider + */ + private TextProvider getTextProvider() { + if (textProvider == null) { + TextProviderFactory tpf = new TextProviderFactory(); + if (container != null) { + container.inject(tpf); + } + textProvider = tpf.createInstance(getClass(), this); + } + return textProvider; + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java new file mode 100644 index 0000000..3927610 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java @@ -0,0 +1,265 @@ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; + + +/** + * This is a composite {@link TextProvider} that takes in an array or {@link java.util.List} of {@link TextProvider}s, it will + * consult each of them in order to get a composite result. To know how each method behaves, please refer to the + * javadoc for each methods. + * + * @author tmjee + * @version $Date$ $Id$ + */ +public class CompositeTextProvider implements TextProvider { + + private static final Logger LOG = LogManager.getLogger(CompositeTextProvider.class); + + private List<TextProvider> textProviders = new ArrayList<>(); + + /** + * Instantiates a {@link CompositeTextProvider} with some predefined <code>textProviders</code>. + * + * @param textProviders + */ + public CompositeTextProvider(List<TextProvider> textProviders) { + this.textProviders.addAll(textProviders); + } + + /** + * Instantiates a {@link CompositeTextProvider} with some predefined <code>textProviders</code>. + * + * @param textProviders + */ + public CompositeTextProvider(TextProvider[] textProviders) { + this(Arrays.asList(textProviders)); + } + + /** + * @param key The key to lookup in ressource bundles. + * @return <tt>true</tt>, if the requested key is found in one of the ressource bundles. + * @see {@link com.opensymphony.xwork2.TextProvider#hasKey(String)} + * It will consult each individual {@link TextProvider}s and return true if either one of the + * {@link TextProvider} has such a <code>key></code> else false. + */ + public boolean hasKey(String key) { + // if there's a key in either text providers we are ok, else try the next text provider + for (TextProvider tp : textProviders) { + if (tp.hasKey(key)) { + return true; + } + } + return false; + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code> + * + * @param key The key to lookup in resource bundles. + * @return The i18n text for the requested key. + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String)} + */ + public String getText(String key) { + return getText(key, key, Collections.emptyList()); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code> before returning <code>defaultValue</code> if every else fails. + * + * @param key + * @param defaultValue + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String)} + */ + public String getText(String key, String defaultValue) { + return getText(key, defaultValue, Collections.emptyList()); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>, before returning <code>defaultValue</code> + * if every else fails. + * + * @param key + * @param defaultValue + * @param obj + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String)} + */ + public String getText(String key, String defaultValue, final String obj) { + return getText(key, defaultValue, new ArrayList<Object>() { + { + add(obj); + } + }); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>. + * + * @param key + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, java.util.List)} + */ + public String getText(String key, List<?> args) { + return getText(key, key, args); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>. + * + * @param key + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String[])} + */ + public String getText(String key, String[] args) { + return getText(key, key, args); + } + + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>, before returning <code>defaultValue</code> + * + * @param key + * @param defaultValue + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText#getText(String, String, java.util.List)} + */ + public String getText(String key, String defaultValue, List<?> args) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>, before returning <code>defaultValue</code>. + * + * @param key + * @param defaultValue + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[])} + */ + public String getText(String key, String defaultValue, String[] args) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>, before returning <code>defaultValue</code> + * + * @param key + * @param defaultValue + * @param args + * @param stack + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, java.util.List, com.opensymphony.xwork2.util.ValueStack)} + */ + public String getText(String key, String defaultValue, List<?> args, ValueStack stack) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args, stack); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * <code>key</code>, before returning <code>defaultValue</code> + * + * @param key + * @param defaultValue + * @param args + * @param stack + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[], com.opensymphony.xwork2.util.ValueStack)} + */ + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args, stack); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + + /** + * It will consult each {@link TextProvider}s and return the first non-null {@link ResourceBundle}. + * + * @param bundleName + * @return + * @see {@link TextProvider#getTexts(String)} + */ + public ResourceBundle getTexts(String bundleName) { + // if there's one text provider that gives us a non-null resource bundle for this bundleName, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + ResourceBundle bundle = textProvider.getTexts(bundleName); + if (bundle != null) { + return bundle; + } + } + return null; + } + + /** + * It will consult each {@link com.opensymphony.xwork2.TextProvider}s and return the first non-null {@link ResourceBundle}. + * + * @return + * @see {@link TextProvider#getTexts()} + */ + public ResourceBundle getTexts() { + // if there's one text provider that gives us a non-null resource bundle, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + ResourceBundle bundle = textProvider.getTexts(); + if (bundle != null) { + return bundle; + } + } + return null; + } +} + + http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java new file mode 100644 index 0000000..82c0eec --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -0,0 +1,514 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.profiling.UtilTimerStack; +import ognl.MethodFailedException; +import ognl.NoSuchPropertyException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * The Default ActionInvocation implementation + * + * @author Rainer Hermanns + * @author tmjee + * @version $Date$ $Id$ + * @see com.opensymphony.xwork2.DefaultActionProxy + */ +public class DefaultActionInvocation implements ActionInvocation { + + private static final Logger LOG = LogManager.getLogger(DefaultActionInvocation.class); + + protected Object action; + protected ActionProxy proxy; + protected List<PreResultListener> preResultListeners; + protected Map<String, Object> extraContext; + protected ActionContext invocationContext; + protected Iterator<InterceptorMapping> interceptors; + protected ValueStack stack; + protected Result result; + protected Result explicitResult; + protected String resultCode; + protected boolean executed = false; + protected boolean pushAction = true; + protected ObjectFactory objectFactory; + protected ActionEventListener actionEventListener; + protected ValueStackFactory valueStackFactory; + protected Container container; + protected UnknownHandlerManager unknownHandlerManager; + protected OgnlUtil ognlUtil; + + public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) { + this.extraContext = extraContext; + this.pushAction = pushAction; + } + + @Inject + public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) { + this.unknownHandlerManager = unknownHandlerManager; + } + + @Inject + public void setValueStackFactory(ValueStackFactory fac) { + this.valueStackFactory = fac; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setContainer(Container cont) { + this.container = cont; + } + + @Inject(required=false) + public void setActionEventListener(ActionEventListener listener) { + this.actionEventListener = listener; + } + + @Inject + public void setOgnlUtil(OgnlUtil ognlUtil) { + this.ognlUtil = ognlUtil; + } + + public Object getAction() { + return action; + } + + public boolean isExecuted() { + return executed; + } + + public ActionContext getInvocationContext() { + return invocationContext; + } + + public ActionProxy getProxy() { + return proxy; + } + + /** + * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method + * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the + * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with + * the result params. + * + * @return a Result instance + * @throws Exception + */ + public Result getResult() throws Exception { + Result returnResult = result; + + // If we've chained to other Actions, we need to find the last result + while (returnResult instanceof ActionChainResult) { + ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy(); + + if (aProxy != null) { + Result proxyResult = aProxy.getInvocation().getResult(); + + if ((proxyResult != null) && (aProxy.getExecuteResult())) { + returnResult = proxyResult; + } else { + break; + } + } else { + break; + } + } + + return returnResult; + } + + public String getResultCode() { + return resultCode; + } + + public void setResultCode(String resultCode) { + if (isExecuted()) { + throw new IllegalStateException("Result has already been executed."); + } + this.resultCode = resultCode; + } + + + public ValueStack getStack() { + return stack; + } + + /** + * Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the + * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order + * in which they are registered. Listener registration and execution does not need to be thread-safe. + * + * @param listener to register + */ + public void addPreResultListener(PreResultListener listener) { + if (preResultListeners == null) { + preResultListeners = new ArrayList<>(1); + } + + preResultListeners.add(listener); + } + + public Result createResult() throws Exception { + + if (explicitResult != null) { + Result ret = explicitResult; + explicitResult = null; + + return ret; + } + ActionConfig config = proxy.getConfig(); + Map<String, ResultConfig> results = config.getResults(); + + ResultConfig resultConfig = null; + + try { + resultConfig = results.get(resultCode); + } catch (NullPointerException e) { + LOG.debug("Got NPE trying to read result configuration for resultCode [{}]", resultCode); + } + + if (resultConfig == null) { + // If no result is found for the given resultCode, try to get a wildcard '*' match. + resultConfig = results.get("*"); + } + + if (resultConfig != null) { + try { + return objectFactory.buildResult(resultConfig, invocationContext.getContextMap()); + } catch (Exception e) { + LOG.error("There was an exception while instantiating the result of type {}", resultConfig.getClassName(), e); + throw new XWorkException(e, resultConfig); + } + } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) { + return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode); + } + return null; + } + + /** + * @throws ConfigurationException If no result can be found with the returned code + */ + public String invoke() throws Exception { + String profileKey = "invoke: "; + try { + UtilTimerStack.push(profileKey); + + if (executed) { + throw new IllegalStateException("Action has already executed"); + } + + if (interceptors.hasNext()) { + final InterceptorMapping interceptor = interceptors.next(); + String interceptorMsg = "interceptor: " + interceptor.getName(); + UtilTimerStack.push(interceptorMsg); + try { + resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); + } finally { + UtilTimerStack.pop(interceptorMsg); + } + } else { + resultCode = invokeActionOnly(); + } + + // this is needed because the result will be executed, then control will return to the Interceptor, which will + // return above and flow through again + if (!executed) { + if (preResultListeners != null) { + for (Object preResultListener : preResultListeners) { + PreResultListener listener = (PreResultListener) preResultListener; + + String _profileKey = "preResultListener: "; + try { + UtilTimerStack.push(_profileKey); + listener.beforeResult(this, resultCode); + } + finally { + UtilTimerStack.pop(_profileKey); + } + } + } + + // now execute the result, if we're supposed to + if (proxy.getExecuteResult()) { + executeResult(); + } + + executed = true; + } + + return resultCode; + } + finally { + UtilTimerStack.pop(profileKey); + } + } + + public String invokeActionOnly() throws Exception { + return invokeAction(getAction(), proxy.getConfig()); + } + + protected void createAction(Map<String, Object> contextMap) { + // load action + String timerKey = "actionCreate: " + proxy.getActionName(); + try { + UtilTimerStack.push(timerKey); + action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); + } catch (InstantiationException e) { + throw new XWorkException("Unable to instantiate Action!", e, proxy.getConfig()); + } catch (IllegalAccessException e) { + throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig()); + } catch (Exception e) { + String gripe; + + if (proxy == null) { + gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad"; + } else if (proxy.getConfig() == null) { + gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?"; + } else if (proxy.getConfig().getClassName() == null) { + gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; + } else { + gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; + } + + gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]"); + throw new XWorkException(gripe, e, proxy.getConfig()); + } finally { + UtilTimerStack.pop(timerKey); + } + + if (actionEventListener != null) { + action = actionEventListener.prepare(action, stack); + } + } + + protected Map<String, Object> createContextMap() { + Map<String, Object> contextMap; + + if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) { + // In case the ValueStack was passed in + stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK); + + if (stack == null) { + throw new IllegalStateException("There was a null Stack set into the extra params."); + } + + contextMap = stack.getContext(); + } else { + // create the value stack + // this also adds the ValueStack to its context + stack = valueStackFactory.createValueStack(); + + // create the action context + contextMap = stack.getContext(); + } + + // put extraContext in + if (extraContext != null) { + contextMap.putAll(extraContext); + } + + //put this DefaultActionInvocation into the context map + contextMap.put(ActionContext.ACTION_INVOCATION, this); + contextMap.put(ActionContext.CONTAINER, container); + + return contextMap; + } + + /** + * Uses getResult to get the final Result and executes it + * + * @throws ConfigurationException If not result can be found with the returned code + */ + private void executeResult() throws Exception { + result = createResult(); + + String timerKey = "executeResult: " + getResultCode(); + try { + UtilTimerStack.push(timerKey); + if (result != null) { + result.execute(this); + } else if (resultCode != null && !Action.NONE.equals(resultCode)) { + throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + + " and result " + getResultCode(), proxy.getConfig()); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("No result returned for action {} at {}", getAction().getClass().getName(), proxy.getConfig().getLocation()); + } + } + } finally { + UtilTimerStack.pop(timerKey); + } + } + + public void init(ActionProxy proxy) { + this.proxy = proxy; + Map<String, Object> contextMap = createContextMap(); + + // Setting this so that other classes, like object factories, can use the ActionProxy and other + // contextual information to operate + ActionContext actionContext = ActionContext.getContext(); + + if (actionContext != null) { + actionContext.setActionInvocation(this); + } + + createAction(contextMap); + + if (pushAction) { + stack.push(action); + contextMap.put("action", action); + } + + invocationContext = new ActionContext(contextMap); + invocationContext.setName(proxy.getActionName()); + + // get a new List so we don't get problems with the iterator if someone changes the list + List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors()); + interceptors = interceptorList.iterator(); + } + + protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { + String methodName = proxy.getMethod(); + + LOG.debug("Executing action method = {}", methodName); + + String timerKey = "invokeAction: " + proxy.getActionName(); + try { + UtilTimerStack.push(timerKey); + + Object methodResult; + try { + methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); + } catch (MethodFailedException e) { + // if reason is missing method, try find version with "do" prefix + if (e.getReason() instanceof NoSuchMethodException) { + try { + String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()"; + methodResult = ognlUtil.getValue(altMethodName, getStack().getContext(), action); + } catch (MethodFailedException e1) { + // if still method doesn't exist, try checking UnknownHandlers + if (e1.getReason() instanceof NoSuchMethodException) { + if (unknownHandlerManager.hasUnknownHandlers()) { + try { + methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); + } catch (NoSuchMethodException e2) { + // throw the original one + throw e; + } + } else { + // throw the original one + throw e; + } + // throw the original exception as UnknownHandlers weren't able to handle invocation as well + if (methodResult == null) { + throw e; + } + } else { + // exception isn't related to missing action method, throw it + throw e1; + } + } + } else { + // exception isn't related to missing action method, throw it + throw e; + } + } + return saveResult(actionConfig, methodResult); + } catch (NoSuchPropertyException e) { + throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); + } catch (MethodFailedException e) { + // We try to return the source exception. + Throwable t = e.getCause(); + + if (actionEventListener != null) { + String result = actionEventListener.handleException(t, getStack()); + if (result != null) { + return result; + } + } + if (t instanceof Exception) { + throw (Exception) t; + } else { + throw e; + } + } finally { + UtilTimerStack.pop(timerKey); + } + } + + /** + * Save the result to be used later. + * @param actionConfig current ActionConfig + * @param methodResult the result of the action. + * @return the result code to process. + */ + protected String saveResult(ActionConfig actionConfig, Object methodResult) { + if (methodResult instanceof Result) { + this.explicitResult = (Result) methodResult; + + // Wire the result automatically + container.inject(explicitResult); + return null; + } else { + return (String) methodResult; + } + } + + /** + * Version ready to be serialize + * + * @return instance without reference to {@link Container} + */ + public ActionInvocation serialize() { + DefaultActionInvocation that = this; + that.container = null; + return that; + } + + /** + * Restoring Container + * + * @param actionContext current {@link ActionContext} + * @return instance which can be used to invoke action + */ + public ActionInvocation deserialize(ActionContext actionContext) { + DefaultActionInvocation that = this; + that.container = actionContext.getContainer(); + return that; + } + +}
