diff --git a/xwork-core/pom.xml b/xwork-core/pom.xml new file mode 100644 index 000000000..ddc5e5c87 --- /dev/null +++ b/xwork-core/pom.xml @@ -0,0 +1,252 @@ + + + + 4.0.0 + com.opensymphony + xwork-core + jar + XWork: Core + + + com.opensymphony + xwork-parent + 2.1.7-SNAPSHOT + + + + scm:svn:http://svn.opensymphony.com/svn/xwork/trunk/core + + scm:svn:https://svn.opensymphony.com/svn/xwork/trunk/core + + https://svn.opensymphony.com/svn/xwork/trunk/core + + + + + The OpenSymphony Software License 1.1 + ../src/resources/LICENSE.txt + + This license is derived and fully compatible with the Apache Software + License - see http://www.apache.org/LICENSE.txt + + + + + + + + j4 + + + + org.codehaus.mojo + retrotranslator-maven-plugin + 1.0-alpha-4 + + + package + + translate-project + + + false + true + true + true + false + + + + + + + + + + + + ${basedir}/src/main/java + ${basedir}/src/test/java + + + ${basedir}/src/main/resources + + + + + ${basedir}/src/test/resources + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.4.2 + + false + + ${project.build.testOutputDirectory}/xwork-jar.jar + ${project.build.testOutputDirectory}/xwork-zip.zip + ${project.build.testOutputDirectory}/xwork - jar.jar + ${project.build.testOutputDirectory}/xwork - zip.zip + + + **/*Test.java + + + **/XWorkTestCase.java + **/TestBean.java + **/TestBean2.java + **/TestInterceptor.java + **/AnnotatedTestBean.java + **/ContainerImplTest.java + **/URLUtilTest.java + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + clean + + + + + + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-shade-plugin + 1.2 + + + package + + shade + + + xwork-core + + + junit:junit + commons-logging:commons-logging + opensymphony:ognl + ognl:ognl + jboss:javassist + org.springframework:spring-core + org.springframework:spring-aop + org.springframework:spring-aspects + org.springframework:spring-beans + org.springframework:spring-context + org.springframework:spring-context-support + org.springframework:spring-web + org.springframework:spring-test + mockobjects:mockobjects-core + org.easymock:easymock + aopalliance:aopalliance + aspectwerkz:aspectwerkz-core + org.aspectj:aspectjrt + org.aspectj:aspectjweaver + cglib:cglib + cglib:cglib-nodep + asm:asm-util + org.testng:testng:jdk15 + + + + + commons-lang:commons-lang + + org/apache/commons/lang/StringUtils.class + org/apache/commons/lang/math/NumberUtils.class + org/apache/commons/lang/ObjectUtils*.class + org/apache/commons/lang/StringEscapeUtils.class + org/apache/commons/lang/exception/NestableRuntimeException.class + org/apache/commons/lang/exception/Nestable.class + org/apache/commons/lang/Entities*class + org/apache/commons/lang/UnhandledException.class + org/apache/commons/lang/IntHashMap*class + + + + + + org.objectweb.asm + org.objectweb.asm.xwork + + + org.apache.commons.lang + org.apache.commons.lang.xwork + + + + + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.apache.commons.lang.xwork.*,com.opensymphony.xwork2.* + + + + + bundle-manifest + process-classes + + manifest + + + + + + install + + + + + + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Action.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Action.java new file mode 100644 index 000000000..f10a431b6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Action.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +/** + * All actions may implement this interface, which exposes the execute() method. + *

+ * However, as of XWork 1.1, this is not required and is only here to assist users. You are free to create POJOs + * that honor the same contract defined by this interface without actually implementing the interface. + */ +public interface Action { + + /** + * The action execution was successful. Show result + * view to the end user. + */ + public static final String SUCCESS = "success"; + + /** + * The action execution was successful but do not + * show a view. This is useful for actions that are + * handling the view in another fashion like redirect. + */ + public static final String NONE = "none"; + + /** + * The action execution was a failure. + * Show an error view, possibly asking the + * user to retry entering data. + */ + public static final String ERROR = "error"; + + /** + * The action execution require more input + * in order to succeed. + * This result is typically used if a form + * handling action has been executed so as + * to provide defaults for a form. The + * form associated with the handler should be + * shown to the end user. + *

+ * This result is also used if the given input + * params are invalid, meaning the user + * should try providing input again. + */ + public static final String INPUT = "input"; + + /** + * The action could not execute, since the + * user most was not logged in. The login view + * should be shown. + */ + public static final String LOGIN = "login"; + + + /** + * Where the logic of the action is executed. + * + * @return a string representing the logical result of the execution. + * See constants in this interface for a list of standard result values. + * @throws Exception thrown if a system level exception occurs. + * Note: Application level exceptions should be handled by returning + * an error value, such as Action.ERROR. + */ + public String execute() throws Exception; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java new file mode 100644 index 000000000..170c56796 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java @@ -0,0 +1,291 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.*; + + +/** +* +* +* This result invokes an entire other action, complete with it's own interceptor stack and result. +* +* +* +* This result type takes the following parameters: +* +* +* +*

+* +* +* +* Example: +* +*

+* <package name="public" extends="struts-default">
+*     <!-- Chain creatAccount to login, using the default parameter -->
+*     <action name="createAccount" class="...">
+*         <result type="chain">login</result>
+*     </action>
+*
+*     <action name="login" class="...">
+*         <!-- Chain to another namespace -->
+*         <result type="chain">
+*             <param name="actionName">dashboard</param>
+*             <param name="namespace">/secure</param>
+*         </result>
+*     </action>
+* </package>
+*
+* <package name="secure" extends="struts-default" namespace="/secure">
+*     <action name="dashboard" class="...">
+*         <result>dashboard.jsp</result>
+*     </action>
+* </package>
+* 
+* +* @author Alexandru Popescu +*/ +public class ActionChainResult implements Result { + + private static final Logger LOG = LoggerFactory.getLogger(ActionChainResult.class); + + /** + * The result parameter name to set the name of the action to chain to. + */ + public static final String DEFAULT_PARAM = "actionName"; + + /** + * The action context key to save the chain history. + */ + private static final String CHAIN_HISTORY = "CHAIN_HISTORY"; + + /** + * The result parameter name to set the name of the action to chain to. + */ + public static final String SKIP_ACTIONS_PARAM = "skipActions"; + + + private ActionProxy proxy; + private String actionName; + + private String namespace; + + private String methodName; + + /** + * The list of actions to skip. + */ + private String skipActions; + + private ActionProxyFactory actionProxyFactory; + + public ActionChainResult() { + super(); + } + + public ActionChainResult(String namespace, String actionName, String methodName) { + this.namespace = namespace; + this.actionName = actionName; + this.methodName = methodName; + } + + public ActionChainResult(String namespace, String actionName, String methodName, String skipActions) { + this.namespace = namespace; + this.actionName = actionName; + this.methodName = methodName; + this.skipActions = skipActions; + } + + + /** + * @param actionProxyFactory the actionProxyFactory to set + */ + @Inject + public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) { + this.actionProxyFactory = actionProxyFactory; + } + + /** + * Set the action name. + * + * @param actionName The action name. + */ + public void setActionName(String actionName) { + this.actionName = actionName; + } + + /** + * sets the namespace of the Action that we're chaining to. if namespace + * is null, this defaults to the current namespace. + * + * @param namespace the name of the namespace we're chaining to + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Set the list of actions to skip. + * To test if an action should not throe an infinite recursion, + * only the action name is used, not the namespace. + * + * @param actions The list of action name separated by a white space. + */ + public void setSkipActions(String actions) { + this.skipActions = actions; + } + + + public void setMethod(String method) { + this.methodName = method; + } + + public ActionProxy getProxy() { + return proxy; + } + + /** + * Get the XWork chain history. + * The stack is a list of namespace/action!method keys. + */ + public static LinkedList getChainHistory() { + LinkedList chainHistory = (LinkedList) ActionContext.getContext().get(CHAIN_HISTORY); + // Add if not exists + if (chainHistory == null) { + chainHistory = new LinkedList(); + ActionContext.getContext().put(CHAIN_HISTORY, chainHistory); + } + + return chainHistory; + } + + /** + * @param invocation the DefaultActionInvocation calling the action call stack + */ + public void execute(ActionInvocation invocation) throws Exception { + // if the finalNamespace wasn't explicitly defined, assume the current one + if (this.namespace == null) { + this.namespace = invocation.getProxy().getNamespace(); + } + + ValueStack stack = ActionContext.getContext().getValueStack(); + String finalNamespace = TextParseUtil.translateVariables(namespace, stack); + String finalActionName = TextParseUtil.translateVariables(actionName, stack); + String finalMethodName = this.methodName != null + ? TextParseUtil.translateVariables(this.methodName, stack) + : null; + + if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) { + addToHistory(finalNamespace, finalActionName, finalMethodName); + throw new XWorkException("Infinite recursion detected: " + + ActionChainResult.getChainHistory().toString()); + } + + if (ActionChainResult.getChainHistory().isEmpty() && invocation != null && invocation.getProxy() != null) { + addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod()); + } + addToHistory(finalNamespace, finalActionName, finalMethodName); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.VALUE_STACK, ActionContext.getContext().getValueStack()); + extraContext.put(ActionContext.PARAMETERS, ActionContext.getContext().getParameters()); + extraContext.put(CHAIN_HISTORY, ActionChainResult.getChainHistory()); + + if (LOG.isDebugEnabled()) { + LOG.debug("Chaining to action " + finalActionName); + } + + proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext); + proxy.execute(); + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final ActionChainResult that = (ActionChainResult) o; + + if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) return false; + if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false; + if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false; + + return true; + } + + @Override public int hashCode() { + int result; + result = (actionName != null ? actionName.hashCode() : 0); + result = 31 * result + (namespace != null ? namespace.hashCode() : 0); + result = 31 * result + (methodName != null ? methodName.hashCode() : 0); + return result; + } + + private boolean isInChainHistory(String namespace, String actionName, String methodName) { + LinkedList chainHistory = ActionChainResult.getChainHistory(); + + if (chainHistory == null) { + return false; + } else { + // Actions to skip + Set skipActionsList = new HashSet(); + if (skipActions != null && skipActions.length() > 0) { + ValueStack stack = ActionContext.getContext().getValueStack(); + String finalSkipActions = TextParseUtil.translateVariables(this.skipActions, stack); + skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions)); + } + if (!skipActionsList.contains(actionName)) { + // Get if key is in the chain history + return chainHistory.contains(makeKey(namespace, actionName, methodName)); + } + + return false; + } + } + + private void addToHistory(String namespace, String actionName, String methodName) { + List chainHistory = ActionChainResult.getChainHistory(); + chainHistory.add(makeKey(namespace, actionName, methodName)); + } + + private String makeKey(String namespace, String actionName, String methodName) { + if (null == methodName) { + return namespace + "/" + actionName; + } + + return namespace + "/" + actionName + "!" + methodName; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionContext.java new file mode 100644 index 000000000..c8ed44b1e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionContext.java @@ -0,0 +1,369 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.util.ValueStack; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + + +/** + * The ActionContext is the context in which an {@link Action} is executed. Each context is basically a + * container of objects an action needs for execution like the session, parameters, locale, etc.

+ *

+ * The ActionContext is thread local which means that values stored in the ActionContext are + * unique per thread. See the {@link ThreadLocal} class for more information. The benefit of + * this is you don't need to worry about a user specific action context, you just get it: + *

+ *

    ActionContext context = ActionContext.getContext();
+ *

+ * Finally, because of the thread local usage you don't need to worry about making your actions thread safe. + * + * @author Patrick Lightbody + * @author Bill Lynch (docs) + */ +public class ActionContext implements Serializable { + static ThreadLocal actionContext = new ThreadLocal(); + + /** + * Constant that indicates the action is running under a "development mode". + * This mode provides more feedback that is useful for developers but probably + * too verbose/error prone for production. + */ + //public static final String DEV_MODE = "__devMode"; + + /** + * Constant for the name of the action being executed. + */ + public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; + + /** + * Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}. + */ + public static final String VALUE_STACK = ValueStack.VALUE_STACK; + + /** + * Constant for the action's session. + */ + public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session"; + + /** + * Constant for the action's application context. + */ + public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application"; + + /** + * Constant for the action's parameters. + */ + public static final String PARAMETERS = "com.opensymphony.xwork2.ActionContext.parameters"; + + /** + * Constant for the action's locale. + */ + public static final String LOCALE = "com.opensymphony.xwork2.ActionContext.locale"; + + /** + * Constant for the action's type converter. + */ + public static final String TYPE_CONVERTER = "com.opensymphony.xwork2.ActionContext.typeConverter"; + + /** + * Constant for the action's {@link com.opensymphony.xwork2.ActionInvocation invocation} context. + */ + public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation"; + + /** + * Constant for the map of type conversion errors. + */ + public static final String CONVERSION_ERRORS = "com.opensymphony.xwork2.ActionContext.conversionErrors"; + + + /** + * Constant for the container + */ + public static final String CONTAINER = "com.opensymphony.xwork2.ActionContext.container"; + + Map context; + + + /** + * Creates a new ActionContext initialized with another context. + * + * @param context a context map. + */ + public ActionContext(Map context) { + this.context = context; + } + + + /** + * Sets the action invocation (the execution state). + * + * @param actionInvocation the action execution state. + */ + public void setActionInvocation(ActionInvocation actionInvocation) { + put(ACTION_INVOCATION, actionInvocation); + } + + /** + * Gets the action invocation (the execution state). + * + * @return the action invocation (the execution state). + */ + public ActionInvocation getActionInvocation() { + return (ActionInvocation) get(ACTION_INVOCATION); + } + + /** + * Sets the action's application context. + * + * @param application the action's application context. + */ + public void setApplication(Map application) { + put(APPLICATION, application); + } + + /** + * Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise. + * + * @return a Map of ServletContext or generic application level Map + */ + public Map getApplication() { + return (Map) get(APPLICATION); + } + + /** + * Sets the action context for the current thread. + * + * @param context the action context. + */ + public static void setContext(ActionContext context) { + actionContext.set(context); + } + + /** + * Returns the ActionContext specific to the current thread. + * + * @return the ActionContext for the current thread, is never null. + */ + public static ActionContext getContext() { + return (ActionContext) actionContext.get(); + + // Don't do lazy context creation, as it requires container; the creation of which may + // precede the context creation + //if (context == null) { + // ValueStack vs = ValueStackFactory.getFactory().createValueStack(); + // context = new ActionContext(vs.getContext()); + // setContext(context); + //} + + } + + /** + * Sets the action's context map. + * + * @param contextMap the context map. + */ + public void setContextMap(Map contextMap) { + getContext().context = contextMap; + } + + /** + * Gets the context map. + * + * @return the context map. + */ + public Map getContextMap() { + return context; + } + + /** + * Sets conversion errors which occurred when executing the action. + * + * @param conversionErrors a Map of errors which occurred when executing the action. + */ + public void setConversionErrors(Map conversionErrors) { + put(CONVERSION_ERRORS, conversionErrors); + } + + /** + * Gets the map of conversion errors which occurred when executing the action. + * + * @return the map of conversion errors which occurred when executing the action or an empty map if + * there were no errors. + */ + public Map getConversionErrors() { + Map errors = (Map) get(CONVERSION_ERRORS); + + if (errors == null) { + errors = new HashMap(); + setConversionErrors(errors); + } + + return errors; + } + + /** + * Sets the Locale for the current action. + * + * @param locale the Locale for the current action. + */ + public void setLocale(Locale locale) { + put(LOCALE, locale); + } + + /** + * Gets the Locale of the current action. If no locale was ever specified the platform's + * {@link java.util.Locale#getDefault() default locale} is used. + * + * @return the Locale of the current action. + */ + public Locale getLocale() { + Locale locale = (Locale) get(LOCALE); + + if (locale == null) { + locale = Locale.getDefault(); + setLocale(locale); + } + + return locale; + } + + /** + * Sets the name of the current Action in the ActionContext. + * + * @param name the name of the current action. + */ + public void setName(String name) { + put(ACTION_NAME, name); + } + + /** + * Gets the name of the current Action. + * + * @return the name of the current action. + */ + public String getName() { + return (String) get(ACTION_NAME); + } + + /** + * Sets the action parameters. + * + * @param parameters the parameters for the current action. + */ + public void setParameters(Map parameters) { + put(PARAMETERS, parameters); + } + + /** + * Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of + * parameters otherwise. + * + * @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a + * generic Map of parameters otherwise. + */ + public Map getParameters() { + return (Map) get(PARAMETERS); + } + + /** + * Sets a map of action session values. + * + * @param session the session values. + */ + public void setSession(Map session) { + put(SESSION, session); + } + + /** + * Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise. + * + * @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise. + */ + public Map getSession() { + return (Map) get(SESSION); + } + + /** + * Sets the OGNL value stack. + * + * @param stack the OGNL value stack. + */ + public void setValueStack(ValueStack stack) { + put(VALUE_STACK, stack); + } + + /** + * Gets the OGNL value stack. + * + * @return the OGNL value stack. + */ + public ValueStack getValueStack() { + return (ValueStack) get(VALUE_STACK); + } + + /** + * Gets the container for this request + * + * @param cont The container + */ + public void setContainer(Container cont) { + put(CONTAINER, cont); + } + + /** + * Sets the container for this request + * + * @return The container + */ + public Container getContainer() { + return (Container) get(CONTAINER); + } + + public T getInstance(Class type) { + Container cont = getContainer(); + if (cont != null) { + return cont.getInstance(type); + } else { + throw new XWorkException("Cannot find an initialized container for this request."); + } + } + + /** + * Returns a value that is stored in the current ActionContext by doing a lookup using the value's key. + * + * @param key the key used to find the value. + * @return the value that was found using the key or null if the key was not found. + */ + public Object get(String key) { + return context.get(key); + } + + /** + * Stores a value in the current ActionContext. The value can be looked up using the key. + * + * @param key the key of the value. + * @param value the value to be stored. + */ + public void put(String key, Object value) { + context.put(key, value); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java new file mode 100644 index 000000000..58c992a51 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.ValueStack; + +/** + * Provides hooks for handling key action events + */ +public interface ActionEventListener { + /** + * Called after an action has been created. + * + * @param action The action + * @param stack The current value stack + * @return The action to use + */ + public Object prepare(Object action, ValueStack stack); + + /** + * Called when an exception is thrown by the action + * + * @param t The exception/error that was thrown + * @param stack The current value stack + * @return A result code to execute, can be null + */ + public String handleException(Throwable t, ValueStack stack); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java new file mode 100644 index 000000000..4154a0afb --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.ValueStack; + +import java.io.Serializable; + + +/** + * An {@link ActionInvocation} represents the execution state of an {@link Action}. It holds the Interceptors and the Action instance. + * By repeated re-entrant execution of the invoke() method, initially by the {@link ActionProxy}, then by the Interceptors, the + * Interceptors are all executed, and then the {@link Action} and the {@link Result}. + * + * @author Jason Carreira + * @see com.opensymphony.xwork2.ActionProxy + */ +public interface ActionInvocation extends Serializable { + + /** + * Get the Action associated with this ActionInvocation. + * + * @return the Action + */ + Object getAction(); + + /** + * Gets whether this ActionInvocation has executed before. + * This will be set after the Action and the Result have executed. + * + * @return true if this ActionInvocation has executed before. + */ + boolean isExecuted(); + + /** + * Gets the ActionContext associated with this ActionInvocation. The ActionProxy is + * responsible for setting this ActionContext onto the ThreadLocal before invoking + * the ActionInvocation and resetting the old ActionContext afterwards. + * + * @return the ActionContext. + */ + ActionContext getInvocationContext(); + + /** + * Get the ActionProxy holding this ActionInvocation. + * + * @return the ActionProxy. + */ + ActionProxy getProxy(); + + /** + * If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method + * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the + * ActionInvocation's result has not been executed before, the Result instance will be created and populated with + * the result params. + * + * @return the result. + * @throws Exception can be thrown. + */ + Result getResult() throws Exception; + + /** + * Gets the result code returned from this ActionInvocation. + * + * @return the result code + */ + String getResultCode(); + + /** + * Sets the result code, possibly overriding the one returned by the + * action. + *

+ * The "intended" purpose of this method is to allow PreResultListeners to + * override the result code returned by the Action. + *

+ * If this method is used before the Action executes, the Action's returned + * result code will override what was set. However the Action could (if + * specifically coded to do so) inspect the ActionInvocation to see that + * someone "upstream" (e.g. an Interceptor) had suggested a value as the + * result, and it could therefore return the same value itself. + *

+ * If this method is called between the Action execution and the Result + * execution, then the value set here will override the result code the + * action had returned. Creating an Interceptor that implements + * {@link PreResultListener} will give you this oportunity. + *

+ * If this method is called after the Result has been executed, it will + * have the effect of raising an IllegalStateException. + * + * @param resultCode the result code. + * @throws IllegalStateException if called after the Result has been executed. + * @see #isExecuted() + */ + void setResultCode(String resultCode); + + /** + * Gets the ValueStack associated with this ActionInvocation. + * + * @return the ValueStack + */ + ValueStack getStack(); + + /** + * Register a {@link PreResultListener} to be notified after the Action is executed and + * before the Result is executed. + *

+ * The ActionInvocation implementation must guarantee that listeners will be called in + * the order in which they are registered. + *

+ * Listener registration and execution does not need to be thread-safe. + * + * @param listener the listener to add. + */ + void addPreResultListener(PreResultListener listener); + + /** + * Invokes the next step in processing this ActionInvocation. + *

+ * If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit + * ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor + * to execute. If there are no more Interceptors to be applied, the Action is executed. + * If the {@link ActionProxy#getExecuteResult()} method returns true, the Result is also executed. + * + * @throws Exception can be thrown. + * @return the return code. + */ + String invoke() throws Exception; + + /** + * Invokes only the Action (not Interceptors or Results). + *

+ * This is useful in rare situations where advanced usage with the interceptor/action/result workflow is + * being manipulated for certain functionality. + * + * @return the return code. + * @throws Exception can be thrown. + */ + String invokeActionOnly() throws Exception; + + /** + * Sets the action event listener to respond to key action events. + * + * @param listener the listener. + */ + void setActionEventListener(ActionEventListener listener); + + void init(ActionProxy proxy) ; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxy.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxy.java new file mode 100644 index 000000000..916fef387 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxy.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.entities.ActionConfig; + + +/** + * ActionProxy is an extra layer between XWork and the action so that different proxies are possible. + *

+ * An example of this would be a remote proxy, where the layer between XWork and the action might be RMI or SOAP. + * + * @author Jason Carreira + */ +public interface ActionProxy { + + /** + * Gets the Action instance for this Proxy. + * + * @return the Action instance + */ + Object getAction(); + + /** + * Gets the alias name this ActionProxy is mapped to. + * + * @return the alias name + */ + String getActionName(); + + /** + * Gets the ActionConfig this ActionProxy is built from. + * + * @return the ActionConfig + */ + ActionConfig getConfig(); + + /** + * Sets whether this ActionProxy should also execute the Result after executing the Action. + * + * @param executeResult true to also execute the Result. + */ + void setExecuteResult(boolean executeResult); + + /** + * Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed. + * + * @return the status + */ + boolean getExecuteResult(); + + /** + * Gets the ActionInvocation associated with this ActionProxy. + * + * @return the ActionInvocation + */ + ActionInvocation getInvocation(); + + /** + * Gets the namespace the ActionConfig for this ActionProxy is mapped to. + * + * @return the namespace + */ + String getNamespace(); + + /** + * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext + * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal. + * + * @return the result code returned from executing the ActionInvocation + * @throws Exception can be thrown. + * @see ActionInvocation + */ + String execute() throws Exception; + + /** + * Gets the method name to execute, or null if no method has been specified (meaning execute will be invoked). + * + * @return the method to execute + */ + String getMethod(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java new file mode 100644 index 000000000..5cf4de016 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.util.Map; + + +/** + * The {@link ActionProxyFactory} is used to create {@link ActionProxy}s to be executed. + *

+ * It is the entry point to XWork that is used by a dispatcher to create an {@link ActionProxy} to execute + * for a particular namespace and action name. + * + * @author Jason Carreira + * @see DefaultActionProxyFactory + */ +public interface ActionProxyFactory { + + /** + * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy + * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated. + *

+ * Note: This is the most used create method. + * + * @param namespace the namespace of the action, can be null + * @param actionName the name of the action + * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null + * @return ActionProxy the created action proxy + * @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map) instead} + */ + @Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext); + + /** + * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy + * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated. + *

+ * Note: This is the most used create method. + * + * @param namespace the namespace of the action, can be null + * @param actionName the name of the action + * @param methodName the name of the method to execute + * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null + * @return ActionProxy the created action proxy + * @since 2.1.1 + */ + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext); + + /** + * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy + * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated. + * + * @param namespace the namespace of the action, can be null + * @param actionName the name of the action + * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null + * @param executeResult flag which tells whether the result should be executed after the action + * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy. + * @return ActionProxy the created action proxy + * @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map,boolean,boolean)} instead + */ + @Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext, boolean executeResult, boolean cleanupContext); + + /** + * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy + * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated. + * + * @param namespace the namespace of the action, can be null + * @param actionName the name of the action + * @param methodName the name of the method to execute + * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be null + * @param executeResult flag which tells whether the result should be executed after the action + * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy. + * @return ActionProxy the created action proxy + * @since 2.1.1 + */ + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext); + + + /** + * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy + * should be fully initialized when it is returned, including passed {@link ActionInvocation} instance. + * + * @param actionInvocation the action invocation instance to associate with + * @param namespace the namespace of the action, can be null + * @param actionName the name of the action + * @param methodName the name of the method to execute + * @param executeResult flag which tells whether the result should be executed after the action + * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy. + * @return ActionProxy the created action proxy + * @since 2.1.1 + */ + public ActionProxy createActionProxy(ActionInvocation actionInvocation, String namespace, String actionName, String methodName, + boolean executeResult, boolean cleanupContext); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionSupport.java new file mode 100644 index 000000000..530e0b43a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ActionSupport.java @@ -0,0 +1,287 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + + +/** + * Provides a default implementation for the most common actions. + * See the documentation for all the interfaces this class implements for more detailed information. + */ +public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable { + + protected static Logger LOG = LoggerFactory.getLogger(ActionSupport.class); + + private final ValidationAwareSupport validationAware = new ValidationAwareSupport(); + + private transient TextProvider textProvider; + private Container container; + + public void setActionErrors(Collection errorMessages) { + validationAware.setActionErrors(errorMessages); + } + + public Collection getActionErrors() { + return validationAware.getActionErrors(); + } + + public void setActionMessages(Collection messages) { + validationAware.setActionMessages(messages); + } + + public Collection getActionMessages() { + return validationAware.getActionMessages(); + } + + /** + * @deprecated Use {@link #getActionErrors()}. + */ + @Deprecated + public Collection getErrorMessages() { + return getActionErrors(); + } + + /** + * @deprecated Use {@link #getFieldErrors()}. + */ + @Deprecated + public Map> getErrors() { + return getFieldErrors(); + } + + public void setFieldErrors(Map> errorMap) { + validationAware.setFieldErrors(errorMap); + } + + public Map> getFieldErrors() { + return validationAware.getFieldErrors(); + } + + public Locale getLocale() { + ActionContext ctx = ActionContext.getContext(); + if (ctx != null) { + return ctx.getLocale(); + } else { + LOG.debug("Action context not initialized"); + return null; + } + } + + public boolean hasKey(String key) { + return getTextProvider().hasKey(key); + } + + public String getText(String aTextName) { + return getTextProvider().getText(aTextName); + } + + public String getText(String aTextName, String defaultValue) { + return getTextProvider().getText(aTextName, defaultValue); + } + + public String getText(String aTextName, String defaultValue, String obj) { + return getTextProvider().getText(aTextName, defaultValue, obj); + } + + public String getText(String aTextName, List args) { + return getTextProvider().getText(aTextName, args); + } + + public String getText(String key, String[] args) { + return getTextProvider().getText(key, args); + } + + public String getText(String aTextName, String defaultValue, List args) { + return getTextProvider().getText(aTextName, defaultValue, args); + } + + public String getText(String key, String defaultValue, String[] args) { + return getTextProvider().getText(key, defaultValue, args); + } + + public String getText(String key, String defaultValue, List args, ValueStack stack) { + return getTextProvider().getText(key, defaultValue, args, stack); + } + + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + return getTextProvider().getText(key, defaultValue, args, stack); + } + + public ResourceBundle getTexts() { + return getTextProvider().getTexts(); + } + + public ResourceBundle getTexts(String aBundleName) { + return getTextProvider().getTexts(aBundleName); + } + + public void addActionError(String anErrorMessage) { + validationAware.addActionError(anErrorMessage); + } + + public void addActionMessage(String aMessage) { + validationAware.addActionMessage(aMessage); + } + + public void addFieldError(String fieldName, String errorMessage) { + validationAware.addFieldError(fieldName, errorMessage); + } + + public String input() throws Exception { + return INPUT; + } + + public String doDefault() throws Exception { + return SUCCESS; + } + + /** + * A default implementation that does nothing an returns "success". + *

+ * Subclasses should override this method to provide their business logic. + *

+ * See also {@link com.opensymphony.xwork2.Action#execute()}. + * + * @return returns {@link #SUCCESS} + * @throws Exception can be thrown by subclasses. + */ + public String execute() throws Exception { + return SUCCESS; + } + + public boolean hasActionErrors() { + return validationAware.hasActionErrors(); + } + + public boolean hasActionMessages() { + return validationAware.hasActionMessages(); + } + + public boolean hasErrors() { + return validationAware.hasErrors(); + } + + public boolean hasFieldErrors() { + return validationAware.hasFieldErrors(); + } + + /** + * Clears field errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearFieldErrors() { + validationAware.clearFieldErrors(); + } + + /** + * Clears action errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearActionErrors() { + validationAware.clearActionErrors(); + } + + /** + * Clears messages. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearMessages() { + validationAware.clearMessages(); + } + + /** + * Clears all errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearErrors() { + validationAware.clearErrors(); + } + + /** + * Clears all errors and messages. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearErrorsAndMessages() { + validationAware.clearErrorsAndMessages(); + } + + /** + * A default implementation that validates nothing. + * Subclasses should override this method to provide validations. + */ + public void validate() { + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * + * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return + * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc. + *

+ *

+ * The next time this action is invoked (and using the same continuation ID), the method will resume immediately + * after where this method was called, with the entire call stack in the execute method restored. + *

+ *

+ * Note: this method can only be called within the {@link #execute()} method. + * + * + * @param result the result to return - the same type of return value in the {@link #execute()} method. + */ + public void pause(String result) { + } + + /** + * If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory}, + * inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in, + * then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field + * for future references and at the returns reference to that field + * + * @return reference to field with TextProvider + */ + private TextProvider getTextProvider() { + if (textProvider == null) { + TextProviderFactory tpf = new TextProviderFactory(); + if (container != null) { + container.inject(tpf); + } + textProvider = tpf.createInstance(getClass(), this); + } + return textProvider; + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java new file mode 100644 index 000000000..ec54b97c7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java @@ -0,0 +1,267 @@ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.*; + + +/** + * This is a composite {@link TextProvider} that takes in an array or {@link java.util.List} of {@link TextProvider}s, it will + * consult each of them in order to get a composite result. To know how each method behaves, please refer to the + * javadoc for each methods. + * + * @author tmjee + * @version $Date$ $Id$ + */ +public class CompositeTextProvider implements TextProvider { + + private static final Logger LOG = LoggerFactory.getLogger(CompositeTextProvider.class); + + private List textProviders = new ArrayList(); + + /** + * Instantiates a {@link CompositeTextProvider} with some predefined textProviders. + * + * @param textProviders + */ + public CompositeTextProvider(List textProviders) { + this.textProviders.addAll(textProviders); + } + + /** + * Instantiates a {@link CompositeTextProvider} with some predefined textProviders. + * + * @param textProviders + */ + public CompositeTextProvider(TextProvider[] textProviders) { + this(Arrays.asList(textProviders)); + } + + /** + * @param key The key to lookup in ressource bundles. + * @return true, if the requested key is found in one of the ressource bundles. + * @see {@link com.opensymphony.xwork2.TextProvider#hasKey(String)} + * It will consult each individual {@link TextProvider}s and return true if either one of the + * {@link TextProvider} has such a key> else false. + */ + public boolean hasKey(String key) { + // if there's a key in either text providers we are ok, else try the next text provider + for (TextProvider tp : textProviders) { + if (tp.hasKey(key)) { + return true; + } + } + return false; + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key + * + * @param key The key to lookup in ressource bundles. + * @return The i18n text for the requested key. + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String)} + */ + public String getText(String key) { + return getText(key, key, Collections.emptyList()); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key before returning defaultValue if every else fails. + * + * @param key + * @param defaultValue + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String)} + */ + public String getText(String key, String defaultValue) { + return getText(key, defaultValue, Collections.emptyList()); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key, before returining defaultValue + * if every else fails. + * + * @param key + * @param defaultValue + * @param obj + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String)} + */ + public String getText(String key, String defaultValue, final String obj) { + return getText(key, defaultValue, new ArrayList() { + { + add(obj); + } + + + }); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key. + * + * @param key + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, java.util.List)} + */ + public String getText(String key, List args) { + return getText(key, key, args); + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key. + * + * @param key + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String[])} + */ + public String getText(String key, String[] args) { + return getText(key, key, args); + } + + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key, before returining defaultValue + * + * @param key + * @param defaultValue + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText#getText(String, String, java.util.List)} + */ + public String getText(String key, String defaultValue, List args) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key, before returining defaultValue. + * + * @param key + * @param defaultValue + * @param args + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[])} + */ + public String getText(String key, String defaultValue, String[] args) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key, before returining defaultValue + * + * @param key + * @param defaultValue + * @param args + * @param stack + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, java.util.List, com.opensymphony.xwork2.util.OgnlValueStack)} + */ + public String getText(String key, String defaultValue, List args, ValueStack stack) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args, stack); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + /** + * It will consult each {@link TextProvider}s and return the first valid message for this + * key, before returining defaultValue + * + * @param key + * @param defaultValue + * @param args + * @param stack + * @return + * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[], com.opensymphony.xwork2.util.ValueStack)} + */ + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + // if there's one text provider that gives us a msg not the same as defaultValue + // for this key, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + String msg = textProvider.getText(key, defaultValue, args, stack); + if (msg != null && (!msg.equals(defaultValue))) { + return msg; + } + } + return defaultValue; + } + + + /** + * It will consult each {@link TextProvider}s and return the first non-null {@link ResourceBundle}. + * + * @param bundleName + * @return + * @see {@link TextProvider#getTexts(String)} + */ + public ResourceBundle getTexts(String bundleName) { + // if there's one text provider that gives us a non-null resource bunlde for this bundleName, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + ResourceBundle bundle = textProvider.getTexts(bundleName); + if (bundle != null) { + return bundle; + } + } + return null; + } + + /** + * It will consult each {@link com.opensymphony.xwork2.TextProvider}s and return the first non-null {@link ResourceBundle}. + * + * @return + * @see {@link TextProvider#getTexts()} + */ + public ResourceBundle getTexts() { + // if there's one text provider that gives us a non-null resource bundle, we are ok, else try the next + // text provider + for (TextProvider textProvider : textProviders) { + ResourceBundle bundle = textProvider.getTexts(); + if (bundle != null) { + return bundle; + } + } + return null; + } +} + + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java new file mode 100644 index 000000000..0a6b237db --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -0,0 +1,487 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.profiling.UtilTimerStack; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * The Default ActionInvocation implementation + * + * @author Rainer Hermanns + * @author tmjee + * @version $Date$ $Id$ + * @see com.opensymphony.xwork2.DefaultActionProxy + */ +public class DefaultActionInvocation implements ActionInvocation { + + private static final long serialVersionUID = -585293628862447329L; + + //static { + // if (ObjectFactory.getContinuationPackage() != null) { + // continuationHandler = new ContinuationHandler(); + // } + //} + private static final Logger LOG = LoggerFactory.getLogger(DefaultActionInvocation.class); + + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + protected Object action; + protected ActionProxy proxy; + protected List preResultListeners; + protected Map extraContext; + protected ActionContext invocationContext; + protected Iterator interceptors; + protected ValueStack stack; + protected Result result; + protected Result explicitResult; + protected String resultCode; + protected boolean executed = false; + protected boolean pushAction = true; + protected ObjectFactory objectFactory; + protected ActionEventListener actionEventListener; + protected ValueStackFactory valueStackFactory; + protected Container container; + private Configuration configuration; + protected UnknownHandlerManager unknownHandlerManager; + + public DefaultActionInvocation(final Map extraContext, final boolean pushAction) { + DefaultActionInvocation.this.extraContext = extraContext; + DefaultActionInvocation.this.pushAction = pushAction; + } + + @Inject + public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) { + this.unknownHandlerManager = unknownHandlerManager; + } + + @Inject + public void setValueStackFactory(ValueStackFactory fac) { + this.valueStackFactory = fac; + } + + @Inject + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setContainer(Container cont) { + this.container = cont; + } + + @Inject(required=false) + public void setActionEventListener(ActionEventListener listener) { + this.actionEventListener = listener; + } + + public Object getAction() { + return action; + } + + public boolean isExecuted() { + return executed; + } + + public ActionContext getInvocationContext() { + return invocationContext; + } + + public ActionProxy getProxy() { + return proxy; + } + + /** + * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method + * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the + * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with + * the result params. + * + * @return a Result instance + * @throws Exception + */ + public Result getResult() throws Exception { + Result returnResult = result; + + // If we've chained to other Actions, we need to find the last result + while (returnResult instanceof ActionChainResult) { + ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy(); + + if (aProxy != null) { + Result proxyResult = aProxy.getInvocation().getResult(); + + if ((proxyResult != null) && (aProxy.getExecuteResult())) { + returnResult = proxyResult; + } else { + break; + } + } else { + break; + } + } + + return returnResult; + } + + public String getResultCode() { + return resultCode; + } + + public void setResultCode(String resultCode) { + if (isExecuted()) + throw new IllegalStateException("Result has already been executed."); + + this.resultCode = resultCode; + } + + + public ValueStack getStack() { + return stack; + } + + /** + * Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the + * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order + * in which they are registered. Listener registration and execution does not need to be thread-safe. + * + * @param listener + */ + public void addPreResultListener(PreResultListener listener) { + if (preResultListeners == null) { + preResultListeners = new ArrayList(1); + } + + preResultListeners.add(listener); + } + + public Result createResult() throws Exception { + + if (explicitResult != null) { + Result ret = explicitResult; + explicitResult = null; + + return ret; + } + ActionConfig config = proxy.getConfig(); + Map results = config.getResults(); + + ResultConfig resultConfig = null; + + try { + resultConfig = results.get(resultCode); + } catch (NullPointerException e) { + // swallow + } + + if (resultConfig == null) { + // If no result is found for the given resultCode, try to get a wildcard '*' match. + resultConfig = results.get("*"); + } + + if (resultConfig != null) { + try { + return objectFactory.buildResult(resultConfig, invocationContext.getContextMap()); + } catch (Exception e) { + LOG.error("There was an exception while instantiating the result of type " + resultConfig.getClassName(), e); + throw new XWorkException(e, resultConfig); + } + } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) { + return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode); + } + return null; + } + + /** + * @throws ConfigurationException If no result can be found with the returned code + */ + public String invoke() throws Exception { + String profileKey = "invoke: "; + try { + UtilTimerStack.push(profileKey); + + if (executed) { + throw new IllegalStateException("Action has already executed"); + } + + if (interceptors.hasNext()) { + final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); + String interceptorMsg = "interceptor: " + interceptor.getName(); + UtilTimerStack.push(interceptorMsg); + try { + resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); + } + finally { + UtilTimerStack.pop(interceptorMsg); + } + } else { + resultCode = invokeActionOnly(); + } + + // this is needed because the result will be executed, then control will return to the Interceptor, which will + // return above and flow through again + if (!executed) { + if (preResultListeners != null) { + for (Object preResultListener : preResultListeners) { + PreResultListener listener = (PreResultListener) preResultListener; + + String _profileKey = "preResultListener: "; + try { + UtilTimerStack.push(_profileKey); + listener.beforeResult(this, resultCode); + } + finally { + UtilTimerStack.pop(_profileKey); + } + } + } + + // now execute the result, if we're supposed to + if (proxy.getExecuteResult()) { + executeResult(); + } + + executed = true; + } + + return resultCode; + } + finally { + UtilTimerStack.pop(profileKey); + } + } + + public String invokeActionOnly() throws Exception { + return invokeAction(getAction(), proxy.getConfig()); + } + + protected void createAction(Map contextMap) { + // load action + String timerKey = "actionCreate: " + proxy.getActionName(); + try { + UtilTimerStack.push(timerKey); + action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); + } catch (InstantiationException e) { + throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig()); + } catch (IllegalAccessException e) { + throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig()); + } catch (Exception e) { + String gripe = ""; + + if (proxy == null) { + gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad"; + } else if (proxy.getConfig() == null) { + gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?"; + } else if (proxy.getConfig().getClassName() == null) { + gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; + } else { + gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; + } + + gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]"); + throw new XWorkException(gripe, e, proxy.getConfig()); + } finally { + UtilTimerStack.pop(timerKey); + } + + if (actionEventListener != null) { + action = actionEventListener.prepare(action, stack); + } + } + + protected Map createContextMap() { + Map contextMap; + + if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) { + // In case the ValueStack was passed in + stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK); + + if (stack == null) { + throw new IllegalStateException("There was a null Stack set into the extra params."); + } + + contextMap = stack.getContext(); + } else { + // create the value stack + // this also adds the ValueStack to its context + stack = valueStackFactory.createValueStack(); + + // create the action context + contextMap = stack.getContext(); + } + + // put extraContext in + if (extraContext != null) { + contextMap.putAll(extraContext); + } + + //put this DefaultActionInvocation into the context map + contextMap.put(ActionContext.ACTION_INVOCATION, this); + contextMap.put(ActionContext.CONTAINER, container); + + return contextMap; + } + + /** + * Uses getResult to get the final Result and executes it + * + * @throws ConfigurationException If not result can be found with the returned code + */ + private void executeResult() throws Exception { + result = createResult(); + + String timerKey = "executeResult: " + getResultCode(); + try { + UtilTimerStack.push(timerKey); + if (result != null) { + result.execute(this); + } else if (resultCode != null && !Action.NONE.equals(resultCode)) { + throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + + " and result " + getResultCode(), proxy.getConfig()); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation()); + } + } + } finally { + UtilTimerStack.pop(timerKey); + } + } + + public void init(ActionProxy proxy) { + this.proxy = proxy; + Map contextMap = createContextMap(); + + // Setting this so that other classes, like object factories, can use the ActionProxy and other + // contextual information to operate + ActionContext actionContext = ActionContext.getContext(); + + if (actionContext != null) { + actionContext.setActionInvocation(this); + } + + createAction(contextMap); + + if (pushAction) { + stack.push(action); + contextMap.put("action", action); + } + + invocationContext = new ActionContext(contextMap); + invocationContext.setName(proxy.getActionName()); + + // get a new List so we don't get problems with the iterator if someone changes the list + List interceptorList = new ArrayList(proxy.getConfig().getInterceptors()); + interceptors = interceptorList.iterator(); + } + + protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { + String methodName = proxy.getMethod(); + + if (LOG.isDebugEnabled()) { + LOG.debug("Executing action method = " + actionConfig.getMethodName()); + } + + String timerKey = "invokeAction: " + proxy.getActionName(); + try { + UtilTimerStack.push(timerKey); + + boolean methodCalled = false; + Object methodResult = null; + Method method = null; + try { + method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY); + } catch (NoSuchMethodException e) { + // hmm -- OK, try doXxx instead + try { + String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); + method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY); + } catch (NoSuchMethodException e1) { + // well, give the unknown handler a shot + if (unknownHandlerManager.hasUnknownHandlers()) { + try { + methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); + methodCalled = true; + } catch (NoSuchMethodException e2) { + // throw the original one + throw e; + } + } else { + throw e; + } + } + } + + if (!methodCalled) { + methodResult = method.invoke(action, new Object[0]); + } + + if (methodResult instanceof Result) { + this.explicitResult = (Result) methodResult; + + // Wire the result automatically + container.inject(explicitResult); + return null; + } else { + return (String) methodResult; + } + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); + } catch (InvocationTargetException e) { + // We try to return the source exception. + Throwable t = e.getTargetException(); + + if (actionEventListener != null) { + String result = actionEventListener.handleException(t, getStack()); + if (result != null) { + return result; + } + } + if (t instanceof Exception) { + throw (Exception) t; + } else { + throw e; + } + } finally { + UtilTimerStack.pop(timerKey); + } + } + + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java new file mode 100644 index 000000000..ef0d3b4d0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java @@ -0,0 +1,204 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.profiling.UtilTimerStack; + +import java.io.Serializable; +import java.util.Locale; + +import org.apache.commons.lang.StringUtils; + + +/** + * The Default ActionProxy implementation + * + * @author Rainer Hermanns + * @author Revised by Henry Hu + * @author tmjee + * + * @version $Date$ $Id$ + * @since 2005-8-6 + */ +public class DefaultActionProxy implements ActionProxy, Serializable { + + private static final long serialVersionUID = 3293074152487468527L; + + private static final Logger LOG = LoggerFactory.getLogger(DefaultActionProxy.class); + + protected Configuration configuration; + protected ActionConfig config; + protected ActionInvocation invocation; + protected UnknownHandlerManager unknownHandlerManager; + protected String actionName; + protected String namespace; + protected String method; + protected boolean executeResult; + protected boolean cleanupContext; + + protected ObjectFactory objectFactory; + + protected ActionEventListener actionEventListener; + + /** + * This constructor is private so the builder methods (create*) should be used to create an DefaultActionProxy. + *

+ * The reason for the builder methods is so that you can use a subclass to create your own DefaultActionProxy instance + * (like a RMIActionProxy). + */ + protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { + + this.invocation = inv; + this.cleanupContext = cleanupContext; + if (LOG.isDebugEnabled()) { + LOG.debug("Creating an DefaultActionProxy for namespace " + namespace + " and action name " + actionName); + } + + this.actionName = actionName; + this.namespace = namespace; + this.executeResult = executeResult; + this.method = methodName; + } + + @Inject + public void setObjectFactory(ObjectFactory factory) { + this.objectFactory = factory; + } + + @Inject + public void setConfiguration(Configuration config) { + this.configuration = config; + } + + @Inject + public void setUnknownHandler(UnknownHandlerManager unknownHandlerManager) { + this.unknownHandlerManager = unknownHandlerManager; + } + + @Inject(required=false) + public void setActionEventListener(ActionEventListener listener) { + this.actionEventListener = listener; + } + + public Object getAction() { + return invocation.getAction(); + } + + public String getActionName() { + return actionName; + } + + public ActionConfig getConfig() { + return config; + } + + public void setExecuteResult(boolean executeResult) { + this.executeResult = executeResult; + } + + public boolean getExecuteResult() { + return executeResult; + } + + public ActionInvocation getInvocation() { + return invocation; + } + + public String getNamespace() { + return namespace; + } + + public String execute() throws Exception { + ActionContext nestedContext = ActionContext.getContext(); + ActionContext.setContext(invocation.getInvocationContext()); + + String retCode = null; + + String profileKey = "execute: "; + try { + UtilTimerStack.push(profileKey); + + retCode = invocation.invoke(); + } finally { + if (cleanupContext) { + ActionContext.setContext(nestedContext); + } + UtilTimerStack.pop(profileKey); + } + + return retCode; + } + + + public String getMethod() { + return method; + } + + private void resolveMethod() { + // if the method is set to null, use the one from the configuration + // if the one from the configuration is also null, use "execute" + if (StringUtils.isEmpty(this.method)) { + this.method = config.getMethodName(); + if (StringUtils.isEmpty(this.method)) { + this.method = "execute"; + } + } + } + + protected void prepare() { + String profileKey = "create DefaultActionProxy: "; + try { + UtilTimerStack.push(profileKey); + config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); + + if (config == null && unknownHandlerManager.hasUnknownHandlers()) { + config = unknownHandlerManager.handleUnknownAction(namespace, actionName); + } + if (config == null) { + String message; + + if ((namespace != null) && (namespace.trim().length() > 0)) { + message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ + namespace, actionName + }); + } else { + message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ + actionName + }); + } + throw new ConfigurationException(message); + } + + resolveMethod(); + + if (!config.isAllowedMethod(method)) { + throw new ConfigurationException("Invalid method: "+method+" for action "+actionName); + } + + invocation.init(this); + + } finally { + UtilTimerStack.pop(profileKey); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxyFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxyFactory.java new file mode 100644 index 000000000..ba0e8fa3f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultActionProxyFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; + +import java.util.Map; + + +/** + * Default factory for {@link com.opensymphony.xwork2.ActionProxyFactory}. + * + * @author Jason Carreira + */ +public class DefaultActionProxyFactory implements ActionProxyFactory { + + protected Container container; + + public DefaultActionProxyFactory() { + super(); + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + + public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext) { + return createActionProxy(namespace, actionName, null, extraContext, true, true); + } + + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext) { + return createActionProxy(namespace, actionName, methodName, extraContext, true, true); + } + + public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext, boolean executeResult, boolean cleanupContext) { + return createActionProxy(namespace, actionName, null, extraContext, executeResult, cleanupContext); + } + + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext) { + + ActionInvocation inv = new DefaultActionInvocation(extraContext, true); + container.inject(inv); + return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); + } + + public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, boolean executeResult, boolean cleanupContext) { + + return createActionProxy(inv, namespace, actionName, null, executeResult, cleanupContext); + } + + public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { + + DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); + container.inject(proxy); + proxy.prepare(); + return proxy; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java new file mode 100644 index 000000000..02d72ec46 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.ValueStack; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; + +/** + * DefaultTextProvider gets texts from only the default resource bundles associated with the + * LocalizedTextUtil. + * + * @author Jason Carreira + * @author Rainer Hermanns + * @see LocalizedTextUtil#addDefaultResourceBundle(String) + */ +public class DefaultTextProvider implements TextProvider, Serializable, Unchainable { + + private static final Object[] EMPTY_ARGS = new Object[0]; + + public DefaultTextProvider() { + } + + public boolean hasKey(String key) { + return getText(key) != null; + } + + public String getText(String key) { + return LocalizedTextUtil.findDefaultText(key, ActionContext.getContext().getLocale()); + } + + public String getText(String key, String defaultValue) { + String text = getText(key); + if (text == null) { + return defaultValue; + } + return text; + } + + public String getText(String key, List args) { + Object[] params; + if (args != null) { + params = args.toArray(); + } else { + params = EMPTY_ARGS; + } + + return LocalizedTextUtil.findDefaultText(key, ActionContext.getContext().getLocale(), params); + } + + public String getText(String key, String[] args) { + Object[] params; + if (args != null) { + params = args; + } else { + params = EMPTY_ARGS; + } + + return LocalizedTextUtil.findDefaultText(key, ActionContext.getContext().getLocale(), params); + } + + public String getText(String key, String defaultValue, List args) { + String text = getText(key, args); + if(text == null && defaultValue == null) { + defaultValue = key; + } + if (text == null && defaultValue != null) { + + MessageFormat format = new MessageFormat(defaultValue); + format.setLocale(ActionContext.getContext().getLocale()); + format.applyPattern(defaultValue); + + Object[] params; + if (args != null) { + params = args.toArray(); + } else { + params = EMPTY_ARGS; + } + + return format.format(params); + } + return text; + } + + public String getText(String key, String defaultValue, String[] args) { + String text = getText(key, args); + if (text == null) { + MessageFormat format = new MessageFormat(defaultValue); + format.setLocale(ActionContext.getContext().getLocale()); + format.applyPattern(defaultValue); + + if (args == null) { + return format.format(EMPTY_ARGS); + } + + return format.format(args); + } + return text; + } + + + public String getText(String key, String defaultValue, String obj) { + List args = new ArrayList(1); + args.add(obj); + return getText(key, defaultValue, args); + } + + public String getText(String key, String defaultValue, List args, ValueStack stack) { + //we're not using the value stack here + return getText(key, defaultValue, args); + } + + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + //we're not using the value stack here + List values = new ArrayList(Arrays.asList(args)); + return getText(key, defaultValue, values); + } + + public ResourceBundle getTexts(String bundleName) { + return LocalizedTextUtil.findResourceBundle(bundleName, ActionContext.getContext().getLocale()); + } + + public ResourceBundle getTexts() { + return null; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultUnknownHandlerManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultUnknownHandlerManager.java new file mode 100644 index 000000000..057a0019f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/DefaultUnknownHandlerManager.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.UnknownHandler; +import com.opensymphony.xwork2.UnknownHandlerManager; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; + +/** + * Default implementation of UnknownHandlerManager + * + * @see com.opensymphony.xwork2.UnknownHandlerManager + */ +public class DefaultUnknownHandlerManager implements UnknownHandlerManager { + protected ArrayList unknownHandlers; + private Configuration configuration; + private Container container; + + @Inject + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + build(); + } + + @Inject + public void setContainer(Container container) { + this.container = container; + build(); + } + + /** + * Builds a list of UnknowHandlers in the order specified by the configured "unknown-handler-stack". + * If "unknown-handler-stack" was not configured, all UnknowHandlers will be returned, in no specific order + */ + protected void build() { + if (configuration != null && container != null) { + List unkownHandlerStack = configuration.getUnknownHandlerStack(); + unknownHandlers = new ArrayList(); + + if (unkownHandlerStack != null && !unkownHandlerStack.isEmpty()) { + //get UnknownHandlers in the specified order + for (UnknownHandlerConfig unknownHandlerConfig : unkownHandlerStack) { + UnknownHandler uh = container.getInstance(UnknownHandler.class, unknownHandlerConfig.getName()); + unknownHandlers.add(uh); + } + } else { + //add all available UnknownHandlers + Set unknowHandlerNames = container.getInstanceNames(UnknownHandler.class); + if (unknowHandlerNames != null) { + for (String unknowHandlerName : unknowHandlerNames) { + UnknownHandler uh = container.getInstance(UnknownHandler.class, unknowHandlerName); + unknownHandlers.add(uh); + } + } + } + } + } + + /** + * Iterate over UnknownHandlers and return the result of the first one that can handle it + */ + public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) { + for (UnknownHandler unknownHandler : unknownHandlers) { + Result result = unknownHandler.handleUnknownResult(actionContext, actionName, actionConfig, resultCode); + if (result != null) + return result; + } + + return null; + } + + /** + * Iterate over UnknownHandlers and return the result of the first one that can handle it + * + * @throws NoSuchMethodException + */ + public Object handleUnknownMethod(Object action, String methodName) throws NoSuchMethodException { + for (UnknownHandler unknownHandler : unknownHandlers) { + Object result = unknownHandler.handleUnknownActionMethod(action, methodName); + if (result != null) + return result; + } + + return null; + } + + /** + * Iterate over UnknownHandlers and return the result of the first one that can handle it + * + * @throws NoSuchMethodException + */ + public ActionConfig handleUnknownAction(String namespace, String actionName) { + for (UnknownHandler unknownHandler : unknownHandlers) { + ActionConfig result = unknownHandler.handleUnknownAction(namespace, actionName); + if (result != null) + return result; + } + + return null; + } + + public boolean hasUnknownHandlers() { + return unknownHandlers != null && !unknownHandlers.isEmpty(); + } + + public List getUnknownHandlers() { + return unknownHandlers; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/InvalidMetadataException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/InvalidMetadataException.java new file mode 100644 index 000000000..eb556f5a4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/InvalidMetadataException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +/** + * InvalidMetadataException + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class InvalidMetadataException extends RuntimeException { + + /** + * Create a new InvalidMetadataException with the supplied error message. + * + * @param msg the error message + */ + public InvalidMetadataException(String msg) { + super(msg); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java new file mode 100644 index 000000000..6fc4ea981 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.util.Locale; + + +/** + * Indicates that the implementing class can provide its own {@link Locale}. + *

+ * This is useful for when an action may wish override the default locale. All that is + * needed is to implement this interface and return your own custom locale. + * The {@link TextProvider} interface uses this interface heavily for retrieving + * internationalized messages from resource bundles. + * + * @author Jason Carreira + */ +public interface LocaleProvider { + + /** + * Gets the provided locale. + * + * @return the locale. + */ + Locale getLocale(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/MockActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/MockActionInvocation.java new file mode 100644 index 000000000..3eea89bce --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/MockActionInvocation.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2; + +/** + * Mock for an {@link ActionInvocation}. + * + * @author plightbo + * @deprecated Please use @see com.opensymphony.xwork2.mock.MockActionInvocation instead + */ +@Deprecated public class MockActionInvocation extends com.opensymphony.xwork2.mock.MockActionInvocation { +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ModelDriven.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ModelDriven.java new file mode 100644 index 000000000..2f5f6c7ef --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ModelDriven.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + + +/** + * ModelDriven Actions provide a model object to be pushed onto the ValueStack + * in addition to the Action itself, allowing a FormBean type approach like Struts. + * + * @author Jason Carreira + */ +public interface ModelDriven { + + /** + * Gets the model to be pushed onto the ValueStack instead of the Action itself. + * + * @return the model + */ + T getModel(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java new file mode 100644 index 000000000..34a0159dc --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java @@ -0,0 +1,262 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.Interceptor; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; +import com.opensymphony.xwork2.validator.Validator; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + + +/** + * ObjectFactory is responsible for building the core framework objects. Users may register their + * own implementation of the ObjectFactory to control instantiation of these Objects. + *

+ * This default implementation uses the {@link #buildBean(Class,java.util.Map) buildBean} + * method to create all classes (interceptors, actions, results, etc). + *

+ * + * @author Jason Carreira + */ +public class ObjectFactory implements Serializable { + private static final Logger LOG = LoggerFactory.getLogger(ObjectFactory.class); + + private transient ClassLoader ccl; + private Container container; + protected ReflectionProvider reflectionProvider; + + @Inject(value="objectFactory.classloader", required=false) + public void setClassLoader(ClassLoader cl) { + this.ccl = cl; + } + + @Inject + public void setReflectionProvider(ReflectionProvider prov) { + this.reflectionProvider = prov; + } + + public ObjectFactory() { + } + + public ObjectFactory(ReflectionProvider prov) { + this.reflectionProvider = prov; + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + + /** + * @deprecated Since 2.1 + */ + @Deprecated public static ObjectFactory getObjectFactory() { + return ActionContext.getContext().getContainer().getInstance(ObjectFactory.class); + } + + /** + * Allows for ObjectFactory implementations that support + * Actions without no-arg constructors. + * + * @return true if no-arg constructor is required, false otherwise + */ + public boolean isNoArgConstructorRequired() { + return true; + } + + /** + * Utility method to obtain the class matched to className. Caches look ups so that subsequent + * lookups will be faster. + * + * @param className The fully qualified name of the class to return + * @return The class itself + * @throws ClassNotFoundException + */ + public Class getClassInstance(String className) throws ClassNotFoundException { + if (ccl != null) { + return ccl.loadClass(className); + } + + return ClassLoaderUtil.loadClass(className, this.getClass()); + } + + /** + * Build an instance of the action class to handle a particular request (eg. web request) + * @param actionName the name the action configuration is set up with in the configuration + * @param namespace the namespace the action is configured in + * @param config the action configuration found in the config for the actionName / namespace + * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} + * @return instance of the action class to handle a web request + * @throws Exception + */ + public Object buildAction(String actionName, String namespace, ActionConfig config, Map extraContext) throws Exception { + return buildBean(config.getClassName(), extraContext); + } + + /** + * Build a generic Java object of the given type. + * + * @param clazz the type of Object to build + * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} + */ + public Object buildBean(Class clazz, Map extraContext) throws Exception { + return clazz.newInstance(); + } + + /** + * @param obj + */ + protected Object injectInternalBeans(Object obj) { + if (obj != null && container != null) { + container.inject(obj); + } + return obj; + } + + /** + * Build a generic Java object of the given type. + * + * @param className the type of Object to build + * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} + */ + public Object buildBean(String className, Map extraContext) throws Exception { + return buildBean(className, extraContext, true); + } + + /** + * Build a generic Java object of the given type. + * + * @param className the type of Object to build + * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} + */ + public Object buildBean(String className, Map extraContext, boolean injectInternal) throws Exception { + Class clazz = getClassInstance(className); + Object obj = buildBean(clazz, extraContext); + if (injectInternal) { + injectInternalBeans(obj); + } + return obj; + } + + /** + * Builds an Interceptor from the InterceptorConfig and the Map of + * parameters from the interceptor reference. Implementations of this method + * should ensure that the Interceptor is parameterized with both the + * parameters from the Interceptor config and the interceptor ref Map (the + * interceptor ref params take precedence), and that the Interceptor.init() + * method is called on the Interceptor instance before it is returned. + * + * @param interceptorConfig the InterceptorConfig from the configuration + * @param interceptorRefParams a Map of params provided in the Interceptor reference in the + * Action mapping or InterceptorStack definition + */ + public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map interceptorRefParams) throws ConfigurationException { + String interceptorClassName = interceptorConfig.getClassName(); + Map thisInterceptorClassParams = interceptorConfig.getParams(); + Map params = (thisInterceptorClassParams == null) ? new HashMap() : new HashMap(thisInterceptorClassParams); + params.putAll(interceptorRefParams); + + String message; + Throwable cause; + + try { + // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context + Interceptor interceptor = (Interceptor) buildBean(interceptorClassName, null); + reflectionProvider.setProperties(params, interceptor); + interceptor.init(); + + return interceptor; + } catch (InstantiationException e) { + cause = e; + message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "]."; + } catch (IllegalAccessException e) { + cause = e; + message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class [" + interceptorClassName + "]."; + } catch (ClassCastException e) { + cause = e; + message = "Class [" + interceptorClassName + "] does not implement com.opensymphony.xwork2.interceptor.Interceptor"; + } catch (Exception e) { + cause = e; + message = "Caught Exception while registering Interceptor class " + interceptorClassName; + } catch (NoClassDefFoundError e) { + cause = e; + message = "Could not load class " + interceptorClassName + ". Perhaps it exists but certain dependencies are not available?"; + } + + throw new ConfigurationException(message, cause, interceptorConfig); + } + + /** + * Build a Result using the type in the ResultConfig and set the parameters in the ResultConfig. + * + * @param resultConfig the ResultConfig found for the action with the result code returned + * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} + */ + public Result buildResult(ResultConfig resultConfig, Map extraContext) throws Exception { + String resultClassName = resultConfig.getClassName(); + Result result = null; + + if (resultClassName != null) { + result = (Result) buildBean(resultClassName, extraContext); + Map params = resultConfig.getParams(); + if (params != null) { + for (Map.Entry paramEntry : params.entrySet()) { + try { + reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), result, extraContext, true); + } catch (ReflectionException ex) { + if (result instanceof ReflectionExceptionHandler) { + ((ReflectionExceptionHandler) result).handle(ex); + } + } + } + } + } + + return result; + } + + /** + * Build a Validator of the given type and set the parameters on it + * + * @param className the type of Validator to build + * @param params property name -> value Map to set onto the Validator instance + * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} + */ + public Validator buildValidator(String className, Map params, Map extraContext) throws Exception { + Validator validator = (Validator) buildBean(className, null); + reflectionProvider.setProperties(params, validator); + + return validator; + } + + static class ContinuationsClassLoader extends ClassLoader { + + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Preparable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Preparable.java new file mode 100644 index 000000000..58a2412cd --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Preparable.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + + +/** + * Preparable Actions will have their prepare() method called if the {@link com.opensymphony.xwork2.interceptor.PrepareInterceptor} + * is applied to the ActionConfig. + * + * @author Jason Carreira + * @see com.opensymphony.xwork2.interceptor.PrepareInterceptor + */ +public interface Preparable { + + /** + * This method is called to allow the action to prepare itself. + * + * @throws Exception thrown if a system level exception occurs. + */ + void prepare() throws Exception; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ResourceBundleTextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ResourceBundleTextProvider.java new file mode 100644 index 000000000..1415e716e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ResourceBundleTextProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.util.ResourceBundle; + +/** + * Extension Interface for TextProvider to help supporting ResourceBundles. + * + * @author Rene Gielen + */ +public interface ResourceBundleTextProvider extends TextProvider { + + /** + * Set the resource bundle to use. + * + * @param bundle the bundle to use. + */ + void setBundle(ResourceBundle bundle); + + /** + * Set the class to use for reading the resource bundle. + * + * @param clazz the class to use for loading. + */ + void setClazz(Class clazz); + + /** + * Set the LocaleProvider to use. + * + * @param localeProvider the LocaleProvider to use. + */ + void setLocaleProvider(LocaleProvider localeProvider); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Result.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Result.java new file mode 100644 index 000000000..3d6537766 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Result.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.io.Serializable; + + +/** + * All results (except for Action.NONE) of an {@link Action} are mapped to a View implementation. + *

+ * Examples of Views might be: + *

    + *
  • SwingPanelView - pops up a new Swing panel
  • + *
  • ActionChainView - executes another action
  • + *
  • SerlvetRedirectView - redirects the HTTP response to a URL
  • + *
  • ServletDispatcherView - dispatches the HTTP response to a URL
  • + *
+ * + * @author plightbo + */ +public interface Result extends Serializable { + + /** + * Represents a generic interface for all action execution results. + * Whether that be displaying a webpage, generating an email, sending a JMS message, etc. + * + * @param invocation the invocation context. + * @throws Exception can be thrown. + */ + public void execute(ActionInvocation invocation) throws Exception; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TestNGXWorkTestCase.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TestNGXWorkTestCase.java new file mode 100644 index 000000000..823e764be --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TestNGXWorkTestCase.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.impl.MockConfiguration; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.util.XWorkTestCaseHelper; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +/** + * Base test class for TestNG unit tests. Provides common XWork variables + * and performs XWork setup and teardown processes + */ +public class TestNGXWorkTestCase { + + protected ConfigurationManager configurationManager; + protected Configuration configuration; + protected Container container; + protected ActionProxyFactory actionProxyFactory; + + @BeforeTest + protected void setUp() throws Exception { + configurationManager = XWorkTestCaseHelper.setUp(); + configuration = new MockConfiguration(); + ((MockConfiguration) configuration).selfRegister(); + container = configuration.getContainer(); + actionProxyFactory = container.getInstance(ActionProxyFactory.class); + } + + @AfterTest + protected void tearDown() throws Exception { + XWorkTestCaseHelper.tearDown(configurationManager); + configurationManager = null; + configuration = null; + container = null; + actionProxyFactory = null; + } + + protected void loadConfigurationProviders(ConfigurationProvider... providers) { + configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers); + configuration = configurationManager.getConfiguration(); + container = configuration.getContainer(); + actionProxyFactory = container.getInstance(ActionProxyFactory.class); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TextProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProvider.java new file mode 100644 index 000000000..742eb4609 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProvider.java @@ -0,0 +1,179 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.List; +import java.util.ResourceBundle; + + +/** + * Provides access to {@link ResourceBundle}s and their underlying text messages. + * Implementing classes can delegate {@link TextProviderSupport}. Messages will be + * searched in multiple resource bundles, startinag with the one associated with + * this particular class (action in most cases), continuing to try the message + * bundle associated with each superclass as well. It will stop once a bundle is + * found that contains the given text. This gives a cascading style that allow + * global texts to be defined for an application base class. + *

+ * You can override {@link LocaleProvider#getLocale()} to change the behaviour of how + * to choose locale for the bundles that are returned. Typically you would + * use the {@link LocaleProvider} interface to get the users configured locale. + *

+ * When you want to use your own implementation for Struts 2 project you have to define following + * bean and constant in struts.xml: + * <bean class="org.demo.MyTextProvider" name="myTextProvider" type="com.opensymphony.xwork2.TextProvider" /> + * <constant name="struts.xworkTextProvider" value="myTextProvider" /> + *

+ * if you want to also use your implemntation for framework's messages define another constant (remember to put + * into it all framework messages) + * <constant name="system" value="myTextProvider" /> + *

+ * Take a look on {@link com.opensymphony.xwork2.ActionSupport} for example TextProvider implemntation. + * + * @author Jason Carreira + * @author Rainer Hermanns + * @see LocaleProvider + * @see TextProviderSupport + */ +public interface TextProvider { + + /** + * Checks if a message key exists. + * + * @param key message key to check for + * @return boolean true if key exists, false otherwise. + */ + boolean hasKey(String key); + + /** + * Gets a message based on a message key, or null if no message is found. + * + * @param key the resource bundle key that is to be searched for + * @return the message as found in the resource bundle, or null if none is found. + */ + String getText(String key); + + /** + * Gets a message based on a key, or, if the message is not found, a supplied + * default value is returned. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + String getText(String key, String defaultValue); + + /** + * Gets a message based on a key using the supplied obj, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param obj obj to be used in a {@link java.text.MessageFormat} message + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + String getText(String key, String defaultValue, String obj); + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or null if no message is found. + * + * @param key the resource bundle key that is to be searched for + * @param args a list args to be used in a {@link java.text.MessageFormat} message + * @return the message as found in the resource bundle, or null if none is found. + */ + String getText(String key, List args); + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or null if no message is found. + * + * @param key the resource bundle key that is to be searched for + * @param args an array args to be used in a {@link java.text.MessageFormat} message + * @return the message as found in the resource bundle, or null if none is found. + */ + String getText(String key, String[] args); + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param args a list args to be used in a {@link java.text.MessageFormat} message + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + String getText(String key, String defaultValue, List args); + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param args an array args to be used in a {@link java.text.MessageFormat} message + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + String getText(String key, String defaultValue, String[] args); + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. Instead of using the value stack in the ActionContext + * this version of the getText() method uses the provided value stack. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param args a list args to be used in a {@link java.text.MessageFormat} message + * @param stack the value stack to use for finding the text + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + String getText(String key, String defaultValue, List args, ValueStack stack); + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. Instead of using the value stack in the ActionContext + * this version of the getText() method uses the provided value stack. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param args an array args to be used in a {@link java.text.MessageFormat} message + * @param stack the value stack to use for finding the text + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + String getText(String key, String defaultValue, String[] args, ValueStack stack); + + /** + * Get the named bundle, such as "com/acme/Foo". + * + * @param bundleName the name of the resource bundle, such as "com/acme/Foo". + * @return the bundle + */ + ResourceBundle getTexts(String bundleName); + + /** + * Get the resource bundle associated with the implementing class (usually an action). + * + * @return the bundle + */ + ResourceBundle getTexts(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderFactory.java new file mode 100644 index 000000000..bcb6d932b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.inject.Inject; + +import java.util.ResourceBundle; + +/** + * This factory enables users to provide and correctly initialize a custom TextProvider. + * + * @author Oleg Gorobets + * @author Rene Gielen + */ +public class TextProviderFactory { + + private TextProvider textProvider; + + @Inject + public void setTextProvider(TextProvider textProvider) { + this.textProvider = textProvider; + } + + protected TextProvider getTextProvider() { + if (this.textProvider == null) { + return new TextProviderSupport(); + } else { + return textProvider; + } + } + + public TextProvider createInstance(Class clazz, LocaleProvider provider) { + TextProvider instance = getTextProvider(); + if (instance instanceof ResourceBundleTextProvider) { + ((ResourceBundleTextProvider) instance).setClazz(clazz); + ((ResourceBundleTextProvider) instance).setLocaleProvider(provider); + } + return instance; + } + + public TextProvider createInstance(ResourceBundle bundle, LocaleProvider provider) { + TextProvider instance = getTextProvider(); + if (instance instanceof ResourceBundleTextProvider) { + ((ResourceBundleTextProvider) instance).setBundle(bundle); + ((ResourceBundleTextProvider) instance).setLocaleProvider(provider); + } + return instance; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderSupport.java new file mode 100644 index 000000000..188976610 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/TextProviderSupport.java @@ -0,0 +1,327 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.*; + + +/** + * Default TextProvider implementation. + * + * @author Jason Carreira + * @author Rainer Hermanns + */ +public class TextProviderSupport implements ResourceBundleTextProvider { + + private Class clazz; + private LocaleProvider localeProvider; + private ResourceBundle bundle; + + /** + * Default constructor + */ + public TextProviderSupport() { + } + + /** + * Constructor. + * + * @param clazz a clazz to use for reading the resource bundle. + * @param provider a locale provider. + */ + public TextProviderSupport(Class clazz, LocaleProvider provider) { + this.clazz = clazz; + this.localeProvider = provider; + } + + /** + * Constructor. + * + * @param bundle the resource bundle. + * @param provider a locale provider. + */ + public TextProviderSupport(ResourceBundle bundle, LocaleProvider provider) { + this.bundle = bundle; + this.localeProvider = provider; + } + + /** + * @param bundle the resource bundle. + */ + public void setBundle(ResourceBundle bundle) { + this.bundle = bundle; + } + + /** + * @param clazz a clazz to use for reading the resource bundle. + */ + public void setClazz(Class clazz) { + this.clazz = clazz; + } + + + /** + * @param localeProvider a locale provider. + */ + public void setLocaleProvider(LocaleProvider localeProvider) { + this.localeProvider = localeProvider; + } + + + /** + * Checks if a key is available in the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. + */ + public boolean hasKey(String key) { + String message; + if (clazz != null) { + message = LocalizedTextUtil.findText(clazz, key, getLocale(), null, new Object[0] ); + } else { + message = LocalizedTextUtil.findText(bundle, key, getLocale(), null, new Object[0]); + } + return message != null; + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. + * + * @param key name of text to be found + * @return value of named text + */ + public String getText(String key) { + return getText(key, key, Collections.emptyList()); + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. If no text is found for this text name, the default value is returned. + * + * @param key name of text to be found + * @param defaultValue the default value which will be returned if no text is found + * @return value of named text + */ + public String getText(String key, String defaultValue) { + return getText(key, defaultValue, Collections.emptyList()); + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. If no text is found for this text name, the default value is returned. + * + * @param key name of text to be found + * @param defaultValue the default value which will be returned if no text is found + * @return value of named text + */ + public String getText(String key, String defaultValue, String arg) { + List args = new ArrayList(); + args.add(arg); + return getText(key, defaultValue, args); + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. If no text is found for this text name, the default value is returned. + * + * @param key name of text to be found + * @param args a List of args to be used in a MessageFormat message + * @return value of named text + */ + public String getText(String key, List args) { + return getText(key, key, args); + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. If no text is found for this text name, the default value is returned. + * + * @param key name of text to be found + * @param args an array of args to be used in a MessageFormat message + * @return value of named text + */ + public String getText(String key, String[] args) { + return getText(key, key, args); + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. If no text is found for this text name, the default value is returned. + * + * @param key name of text to be found + * @param defaultValue the default value which will be returned if no text is found + * @param args a List of args to be used in a MessageFormat message + * @return value of named text + */ + public String getText(String key, String defaultValue, List args) { + Object[] argsArray = ((args != null && !args.equals(Collections.emptyList())) ? args.toArray() : null); + if (clazz != null) { + return LocalizedTextUtil.findText(clazz, key, getLocale(), defaultValue, argsArray); + } else { + return LocalizedTextUtil.findText(bundle, key, getLocale(), defaultValue, argsArray); + } + } + + /** + * Get a text from the resource bundles associated with this action. + * The resource bundles are searched, starting with the one associated + * with this particular action, and testing all its superclasses' bundles. + * It will stop once a bundle is found that contains the given text. This gives + * a cascading style that allow global texts to be defined for an application base + * class. If no text is found for this text name, the default value is returned. + * + * @param key name of text to be found + * @param defaultValue the default value which will be returned if no text is found + * @param args an array of args to be used in a MessageFormat message + * @return value of named text + */ + public String getText(String key, String defaultValue, String[] args) { + if (clazz != null) { + return LocalizedTextUtil.findText(clazz, key, getLocale(), defaultValue, args); + } else { + return LocalizedTextUtil.findText(bundle, key, getLocale(), defaultValue, args); + } + } + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. Instead of using the value stack in the ActionContext + * this version of the getText() method uses the provided value stack. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param args a list args to be used in a {@link java.text.MessageFormat} message + * @param stack the value stack to use for finding the text + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + public String getText(String key, String defaultValue, List args, ValueStack stack) { + Object[] argsArray = ((args != null) ? args.toArray() : null); + Locale locale; + if (stack == null){ + locale = getLocale(); + }else{ + locale = (Locale) stack.getContext().get(ActionContext.LOCALE); + } + if (locale == null) { + locale = getLocale(); + } + if (clazz != null) { + return LocalizedTextUtil.findText(clazz, key, locale, defaultValue, argsArray, stack); + } else { + return LocalizedTextUtil.findText(bundle, key, locale, defaultValue, argsArray, stack); + } + } + + + /** + * Gets a message based on a key using the supplied args, as defined in + * {@link java.text.MessageFormat}, or, if the message is not found, a supplied + * default value is returned. Instead of using the value stack in the ActionContext + * this version of the getText() method uses the provided value stack. + * + * @param key the resource bundle key that is to be searched for + * @param defaultValue the default value which will be returned if no message is found + * @param args an array args to be used in a {@link java.text.MessageFormat} message + * @param stack the value stack to use for finding the text + * @return the message as found in the resource bundle, or defaultValue if none is found + */ + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + Locale locale; + if (stack == null){ + locale = getLocale(); + }else{ + locale = (Locale) stack.getContext().get(ActionContext.LOCALE); + } + if (locale == null) { + locale = getLocale(); + } + if (clazz != null) { + return LocalizedTextUtil.findText(clazz, key, locale, defaultValue, args, stack); + } else { + return LocalizedTextUtil.findText(bundle, key, locale, defaultValue, args, stack); + } + + } + + /** + * Get the named bundle. + *

+ * You can override the getLocale() methodName to change the behaviour of how + * to choose locale for the bundles that are returned. Typically you would + * use the TextProvider interface to get the users configured locale, or use + * your own methodName to allow the user to select the locale and store it in + * the session (by using the SessionAware interface). + * + * @param aBundleName bundle name + * @return a resource bundle + */ + public ResourceBundle getTexts(String aBundleName) { + return LocalizedTextUtil.findResourceBundle(aBundleName, getLocale()); + } + + /** + * Get the resource bundle associated with this action. + * This will be based on the actual subclass that is used. + * + * @return resouce bundle + */ + public ResourceBundle getTexts() { + if (clazz != null) { + return getTexts(clazz.getName()); + } + return bundle; + } + + /** + * Get's the locale from the localeProvider. + * + * @return the locale from the localeProvider. + */ + private Locale getLocale() { + return localeProvider.getLocale(); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Unchainable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Unchainable.java new file mode 100644 index 000000000..19d88ef4f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Unchainable.java @@ -0,0 +1,25 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2; + +/** + * Simple marker interface to indicate an object should not have its properties copied during chaining. + * + * @see com.opensymphony.xwork2.interceptor.ChainingInterceptor + */ +public interface Unchainable { +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java new file mode 100644 index 000000000..033c0d6f5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.entities.ActionConfig; + +/** + * Handles cases when the result or action is unknown. + *

+ * This allows other classes like Struts plugins to provide intelligent defaults easier. + */ +public interface UnknownHandler { + + /** + * Handles the case when an action configuration is unknown. Implementations can return a new ActionConfig + * to be used to process the request. + * + * @param namespace The namespace + * @param actionName The action name + * @return An generated ActionConfig, can return null + * @throws XWorkException + */ + public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException; + + /** + * Handles the case when a result cannot be found for an action and result code. + * + * @param actionContext The action context + * @param actionName The action name + * @param actionConfig The action config + * @param resultCode The returned result code + * @return A result to be executed, can return null + * @throws XWorkException + */ + public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) throws XWorkException; + + /** + * Handles the case when an action method cannot be found. This method is responsible both for finding the method and executing it. + * + * @since 2.1 + * @param action The action object + * @param methodName The method name to call + * @return The result returned from invoking the action method + * @throws NoSuchMethodException If the method cannot be found + */ + public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandlerManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandlerManager.java new file mode 100644 index 000000000..325c0ffe8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/UnknownHandlerManager.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.entities.ActionConfig; + +import java.util.List; + +/** + * An unknown handler manager contains a list of UnknownHandler and iterates on them by order + * + * @see com.opensymphony.xwork2.DefaultUnknownHandlerManager + */ +public interface UnknownHandlerManager { + Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode); + + Object handleUnknownMethod(Object action, String methodName) throws NoSuchMethodException; + + ActionConfig handleUnknownAction(String namespace, String actionName); + + boolean hasUnknownHandlers(); + + List getUnknownHandlers(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/Validateable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/Validateable.java new file mode 100644 index 000000000..889a28440 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/Validateable.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + + +/** + * Provides an interface in which a call for a validation check can be done. + * + * @author Jason Carreira + * @see ActionSupport + * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor + */ +public interface Validateable { + + /** + * Performs validation. + */ + void validate(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAware.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAware.java new file mode 100644 index 000000000..4ae5e842e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAware.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept + * in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs. + * + * @author plightbo + */ +public interface ValidationAware { + + /** + * Set the Collection of Action-level String error messages. + * + * @param errorMessages Collection of String error messages + */ + void setActionErrors(Collection errorMessages); + + /** + * Get the Collection of Action-level error messages for this action. Error messages should not + * be added directly here, as implementations are free to return a new Collection or an + * Unmodifiable Collection. + * + * @return Collection of String error messages + */ + Collection getActionErrors(); + + /** + * Set the Collection of Action-level String messages (not errors). + * + * @param messages Collection of String messages (not errors). + */ + void setActionMessages(Collection messages); + + /** + * Get the Collection of Action-level messages for this action. Messages should not be added + * directly here, as implementations are free to return a new Collection or an Unmodifiable + * Collection. + * + * @return Collection of String messages + */ + Collection getActionMessages(); + + /** + * Set the field error map of fieldname (String) to Collection of String error messages. + * + * @param errorMap field error map + */ + void setFieldErrors(Map> errorMap); + + /** + * Get the field specific errors associated with this action. Error messages should not be added + * directly here, as implementations are free to return a new Collection or an Unmodifiable + * Collection. + * + * @return Map with errors mapped from fieldname (String) to Collection of String error messages + */ + Map> getFieldErrors(); + + /** + * Add an Action-level error message to this Action. + * + * @param anErrorMessage the error message + */ + void addActionError(String anErrorMessage); + + /** + * Add an Action-level message to this Action. + * + * @param aMessage the message + */ + void addActionMessage(String aMessage); + + /** + * Add an error message for a given field. + * + * @param fieldName name of field + * @param errorMessage the error message + */ + void addFieldError(String fieldName, String errorMessage); + + /** + * Check whether there are any Action-level error messages. + * + * @return true if any Action-level error messages have been registered + */ + boolean hasActionErrors(); + + /** + * Checks whether there are any Action-level messages. + * + * @return true if any Action-level messages have been registered + */ + boolean hasActionMessages(); + + /** + * Checks whether there are any action errors or field errors. + *

+ * Note: that this does not have the same meaning as in WW 1.x. + * + * @return (hasActionErrors() || hasFieldErrors()) + */ + boolean hasErrors(); + + /** + * Check whether there are any field errors associated with this action. + * + * @return whether there are any field errors + */ + boolean hasFieldErrors(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAwareSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAwareSupport.java new file mode 100644 index 000000000..27eb93d70 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ValidationAwareSupport.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import java.io.Serializable; +import java.util.*; + +/** + * Provides a default implementation of ValidationAware. Returns new collections for + * errors and messages (defensive copy). + * + * @author Jason Carreira + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ValidationAwareSupport implements ValidationAware, Serializable { + + private Collection actionErrors; + private Collection actionMessages; + private Map> fieldErrors; + + + public synchronized void setActionErrors(Collection errorMessages) { + this.actionErrors = errorMessages; + } + + public synchronized Collection getActionErrors() { + return new ArrayList(internalGetActionErrors()); + } + + public synchronized void setActionMessages(Collection messages) { + this.actionMessages = messages; + } + + public synchronized Collection getActionMessages() { + return new ArrayList(internalGetActionMessages()); + } + + public synchronized void setFieldErrors(Map> errorMap) { + this.fieldErrors = errorMap; + } + + public synchronized Map> getFieldErrors() { + return new LinkedHashMap>(internalGetFieldErrors()); + } + + public synchronized void addActionError(String anErrorMessage) { + internalGetActionErrors().add(anErrorMessage); + } + + public synchronized void addActionMessage(String aMessage) { + internalGetActionMessages().add(aMessage); + } + + public synchronized void addFieldError(String fieldName, String errorMessage) { + final Map> errors = internalGetFieldErrors(); + List thisFieldErrors = errors.get(fieldName); + + if (thisFieldErrors == null) { + thisFieldErrors = new ArrayList(); + errors.put(fieldName, thisFieldErrors); + } + + thisFieldErrors.add(errorMessage); + } + + public synchronized boolean hasActionErrors() { + return (actionErrors != null) && !actionErrors.isEmpty(); + } + + public synchronized boolean hasActionMessages() { + return (actionMessages != null) && !actionMessages.isEmpty(); + } + + public synchronized boolean hasErrors() { + return (hasActionErrors() || hasFieldErrors()); + } + + public synchronized boolean hasFieldErrors() { + return (fieldErrors != null) && !fieldErrors.isEmpty(); + } + + private Collection internalGetActionErrors() { + if (actionErrors == null) { + actionErrors = new ArrayList(); + } + + return actionErrors; + } + + private Collection internalGetActionMessages() { + if (actionMessages == null) { + actionMessages = new ArrayList(); + } + + return actionMessages; + } + + private Map> internalGetFieldErrors() { + if (fieldErrors == null) { + fieldErrors = new LinkedHashMap>(); + } + + return fieldErrors; + } + + /** + * Clears field errors map. + *

+ * Will clear the map that contains field errors. + */ + public synchronized void clearFieldErrors() { + internalGetFieldErrors().clear(); + } + + /** + * Clears action errors list. + *

+ * Will clear the list that contains action errors. + */ + public synchronized void clearActionErrors() { + internalGetActionErrors().clear(); + } + + /** + * Clears messages list. + *

+ * Will clear the list that contains action messages. + */ + public synchronized void clearMessages() { + internalGetActionMessages().clear(); + } + + /** + * Clears all error list/maps. + *

+ * Will clear the map and list that contain + * field errors and action errors. + */ + public synchronized void clearErrors() { + internalGetFieldErrors().clear(); + internalGetActionErrors().clear(); + } + + /** + * Clears all error and messages list/maps. + *

+ * Will clear the maps/lists that contain + * field errors, action errors and action messages. + */ + public synchronized void clearErrorsAndMessages() { + internalGetFieldErrors().clear(); + internalGetActionErrors().clear(); + internalGetActionMessages().clear(); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWork.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWork.java new file mode 100644 index 000000000..fddc75bc6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWork.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Collections; +import java.util.Map; + +/** + * Simple facade to make using XWork standalone easier + */ +public class XWork { + + ConfigurationManager configurationManager; + + public XWork() { + this(new ConfigurationManager()); + } + + public XWork(ConfigurationManager mgr) { + this.configurationManager = mgr; + } + + public void setLoggerFactory(LoggerFactory factory) { + LoggerFactory.setLoggerFactory(factory); + } + + /** + * Executes an action + * + * @param namespace The namespace + * @param name The action name + * @param method The method name + * @throws Exception If anything goes wrong + */ + public void executeAction(String namespace, String name, String method) throws XWorkException { + Map extraContext = Collections.emptyMap(); + executeAction(namespace, name, method, extraContext); + } + + /** + * Executes an action with extra context information + * + * @param namespace The namespace + * @param name The action name + * @param method The method name + * @param extraContext A map of extra context information + * @throws Exception If anything goes wrong + */ + public void executeAction(String namespace, String name, String method, Map extraContext) throws XWorkException { + Configuration config = configurationManager.getConfiguration(); + try { + ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( + namespace, name, method, extraContext, true, false); + + proxy.execute(); + } catch (Exception e) { + throw new XWorkException(e); + } finally { + ActionContext.setContext(null); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkException.java new file mode 100644 index 000000000..3242ce9e6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkException.java @@ -0,0 +1,154 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.location.Locatable; +import com.opensymphony.xwork2.util.location.Location; +import com.opensymphony.xwork2.util.location.LocationUtils; + + +/** + * A generic runtime exception that optionally contains Location information + * + * @author Jason Carreira + */ +public class XWorkException extends RuntimeException implements Locatable { + + private Location location; + + + /** + * Constructs a XWorkException with no detail message. + */ + public XWorkException() { + } + + /** + * Constructs a XWorkException with the specified + * detail message. + * + * @param s the detail message. + */ + public XWorkException(String s) { + this(s, null, null); + } + + /** + * Constructs a XWorkException with the specified + * detail message and target. + * + * @param s the detail message. + * @param target the target of the exception. + */ + public XWorkException(String s, Object target) { + this(s, (Throwable) null, target); + } + + /** + * Constructs a XWorkException with the root cause + * + * @param cause The wrapped exception + */ + public XWorkException(Throwable cause) { + this(null, cause, null); + } + + /** + * Constructs a XWorkException with the root cause and target + * + * @param cause The wrapped exception + * @param target The target of the exception + */ + public XWorkException(Throwable cause, Object target) { + this(null, cause, target); + } + + /** + * Constructs a XWorkException with the specified + * detail message and exception cause. + * + * @param s the detail message. + * @param cause the wrapped exception + */ + public XWorkException(String s, Throwable cause) { + this(s, cause, null); + } + + + /** + * Constructs a XWorkException with the specified + * detail message, cause, and target + * + * @param s the detail message. + * @param cause The wrapped exception + * @param target The target of the exception + */ + public XWorkException(String s, Throwable cause, Object target) { + super(s, cause); + + this.location = LocationUtils.getLocation(target); + if (this.location == Location.UNKNOWN) { + this.location = LocationUtils.getLocation(cause); + } + } + + + /** + * Gets the underlying cause + * + * @return the underlying cause, null if no cause + * @deprecated Use {@link #getCause()} + */ + @Deprecated public Throwable getThrowable() { + return getCause(); + } + + + /** + * Gets the location of the error, if available + * + * @return the location, null if not available + */ + public Location getLocation() { + return this.location; + } + + + /** + * Returns a short description of this throwable object, including the + * location. If no detailed message is available, it will use the message + * of the underlying exception if available. + * + * @return a string representation of this Throwable. + */ + @Override + public String toString() { + String msg = getMessage(); + if (msg == null && getCause() != null) { + msg = getCause().getMessage(); + } + + if (location != null) { + if (msg != null) { + return msg + " - " + location.toString(); + } else { + return location.toString(); + } + } else { + return msg; + } + } +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkMessages.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkMessages.java new file mode 100644 index 000000000..d187acc51 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkMessages.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + + +/** + * Contains constants for some default XWork messages. + * + * @author Jason Carreira + */ +public interface XWorkMessages { + + public static final String ACTION_EXECUTION_ERROR = "xwork.error.action.execution"; + public static final String MISSING_ACTION_EXCEPTION = "xwork.exception.missing-action"; + public static final String MISSING_PACKAGE_ACTION_EXCEPTION = "xwork.exception.missing-package-action"; + public static final String DEFAULT_INVALID_FIELDVALUE = "xwork.default.invalid.fieldvalue"; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java new file mode 100644 index 000000000..c9b4e32f9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.inject.*; +import com.opensymphony.xwork2.test.StubConfigurationProvider; +import com.opensymphony.xwork2.util.XWorkTestCaseHelper; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import junit.framework.TestCase; + + +/** + * Base JUnit TestCase to extend for XWork specific JUnit tests. Uses + * the generic test setup for logic. + * + * @author plightbo + */ +public abstract class XWorkTestCase extends TestCase { + + protected ConfigurationManager configurationManager; + protected Configuration configuration; + protected Container container; + protected ActionProxyFactory actionProxyFactory; + + public XWorkTestCase() { + super(); + } + + @Override + protected void setUp() throws Exception { + configurationManager = XWorkTestCaseHelper.setUp(); + configuration = configurationManager.getConfiguration(); + container = configuration.getContainer(); + actionProxyFactory = container.getInstance(ActionProxyFactory.class); + } + + @Override + protected void tearDown() throws Exception { + XWorkTestCaseHelper.tearDown(configurationManager); + configurationManager = null; + configuration = null; + container = null; + actionProxyFactory = null; + } + + protected void loadConfigurationProviders(ConfigurationProvider... providers) { + configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers); + configuration = configurationManager.getConfiguration(); + container = configuration.getContainer(); + actionProxyFactory = container.getInstance(ActionProxyFactory.class); + } + + protected void loadButAdd(final Class type, final Object impl) { + loadButAdd(type, Container.DEFAULT_NAME, impl); + } + + protected void loadButAdd(final Class type, final String name, final Object impl) { + loadConfigurationProviders(new StubConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, + LocatableProperties props) throws ConfigurationException { + builder.factory(type, name, new Factory() { + public Object create(Context context) throws Exception { + return impl; + } + + }, Scope.SINGLETON); + } + }); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/Configuration.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/Configuration.java new file mode 100644 index 000000000..be1359d5e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/Configuration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config; + +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; +import com.opensymphony.xwork2.inject.Container; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * XWork configuration. + * + * @author Mike + */ +public interface Configuration extends Serializable { + + void rebuildRuntimeConfiguration(); + + PackageConfig getPackageConfig(String name); + + Set getPackageConfigNames(); + + Map getPackageConfigs(); + + /** + * The current runtime configuration. Currently, if changes have been made to the Configuration since the last + * time buildRuntimeConfiguration() was called, you'll need to make sure to. + * + * @return the current runtime configuration + */ + RuntimeConfiguration getRuntimeConfiguration(); + + void addPackageConfig(String name, PackageConfig packageConfig); + + /** + * Removes a package from the the list of packages. Changes to the configuration won't take effect until buildRuntimeConfiguration + * is called. + * @param packageName the name of the package to remove + * @return the package removed (if any) + */ + PackageConfig removePackageConfig(String packageName); + + /** + * Allow the Configuration to clean up any resources that have been used. + */ + void destroy(); + + /** + * @deprecated Since 2.1 + * @param providers + * @throws ConfigurationException + */ + @Deprecated void reload(List providers) throws ConfigurationException; + + /** + * @since 2.1 + * @param containerProviders + * @throws ConfigurationException + */ + List reloadContainer(List containerProviders) throws ConfigurationException; + + /** + * @return the container + */ + Container getContainer(); + + Set getLoadedFileNames(); + + /** + * @since 2.1 + * @return list of unknown handlers + */ + List getUnknownHandlerStack(); + + /** + * @since 2.1 + * @param unknownHandlerStack + */ + void setUnknownHandlerStack(List unknownHandlerStack); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationException.java new file mode 100644 index 000000000..fcbb11bc8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationException.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config; + +import com.opensymphony.xwork2.XWorkException; + + +/** + * ConfigurationException + * + * @author Jason Carreira + */ +public class ConfigurationException extends XWorkException { + + /** + * Constructs a ConfigurationException with no detail message. + */ + public ConfigurationException() { + } + + /** + * Constructs a ConfigurationException with the specified + * detail message. + * + * @param s the detail message. + */ + public ConfigurationException(String s) { + super(s); + } + + /** + * Constructs a ConfigurationException with the specified + * detail message. + * + * @param s the detail message. + */ + public ConfigurationException(String s, Object target) { + super(s, target); + } + + /** + * Constructs a ConfigurationException with no detail message. + */ + public ConfigurationException(Throwable cause) { + super(cause); + } + + /** + * Constructs a ConfigurationException with no detail message. + */ + public ConfigurationException(Throwable cause, Object target) { + super(cause, target); + } + + /** + * Constructs a ConfigurationException with the specified + * detail message. + * + * @param s the detail message. + */ + public ConfigurationException(String s, Throwable cause) { + super(s, cause); + } + + /** + * Constructs a ConfigurationException with the specified + * detail message. + * + * @param s the detail message. + */ + public ConfigurationException(String s, Throwable cause, Object target) { + super(s, cause, target); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationManager.java new file mode 100644 index 000000000..84d89e940 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationManager.java @@ -0,0 +1,271 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config; + +import com.opensymphony.xwork2.config.impl.DefaultConfiguration; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * ConfigurationManager - central for XWork Configuration management, including + * its ConfigurationProvider. + * + * @author Jason Carreira + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ConfigurationManager { + + protected static final Logger LOG = LoggerFactory.getLogger(ConfigurationManager.class); + protected Configuration configuration; + protected Lock providerLock = new ReentrantLock(); + private List containerProviders = new CopyOnWriteArrayList(); + private List packageProviders = new CopyOnWriteArrayList(); + protected String defaultFrameworkBeanName; + + public ConfigurationManager() { + this("xwork"); + } + + public ConfigurationManager(String name) { + this.defaultFrameworkBeanName = name; + } + + /** + * Get the current XWork configuration object. By default an instance of DefaultConfiguration will be returned + * + * @see com.opensymphony.xwork2.config.impl.DefaultConfiguration + */ + public synchronized Configuration getConfiguration() { + if (configuration == null) { + setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); + try { + configuration.reloadContainer(getContainerProviders()); + } catch (ConfigurationException e) { + setConfiguration(null); + throw new ConfigurationException("Unable to load configuration.", e); + } + } else { + conditionalReload(); + } + + return configuration; + } + + public synchronized void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Get the current list of ConfigurationProviders. If no custom ConfigurationProviders have been added, this method + * will return a list containing only the default ConfigurationProvider, XMLConfigurationProvider. if a custom + * ConfigurationProvider has been added, then the XmlConfigurationProvider must be added by hand. + *

+ *

+ * WARNING: This returns only ContainerProviders that can be cast into ConfigurationProviders + * + * @return the list of registered ConfigurationProvider objects + * @see ConfigurationProvider + * @deprecated Since 2.1, use {@link #getContainerProviders()} + */ + @Deprecated public List getConfigurationProviders() { + List contProviders = getContainerProviders(); + List providers = new ArrayList(); + for (ContainerProvider prov : contProviders) { + if (prov instanceof ConfigurationProvider) { + providers.add((ConfigurationProvider) prov); + } + } + return providers; + } + + /** + * Get the current list of ConfigurationProviders. If no custom ConfigurationProviders have been added, this method + * will return a list containing only the default ConfigurationProvider, XMLConfigurationProvider. if a custom + * ConfigurationProvider has been added, then the XmlConfigurationProvider must be added by hand. + *

+ *

+ * TODO: the lazy instantiation of XmlConfigurationProvider should be refactored to be elsewhere. the behavior described above seems unintuitive. + * + * @return the list of registered ConfigurationProvider objects + * @see ConfigurationProvider + */ + public List getContainerProviders() { + providerLock.lock(); + try { + if (containerProviders.size() == 0) { + containerProviders.add(new XWorkConfigurationProvider()); + containerProviders.add(new XmlConfigurationProvider("xwork.xml", false)); + } + + return containerProviders; + } finally { + providerLock.unlock(); + } + } + + /** + * Set the list of configuration providers + * + * @param configurationProviders + * @deprecated Since 2.1, use {@link #setContainerProvider()} + */ + @Deprecated public void setConfigurationProviders(List configurationProviders) { + // Silly copy necessary due to lack of ability to cast generic lists + List contProviders = new ArrayList(); + contProviders.addAll(configurationProviders); + + setContainerProviders(contProviders); + } + + /** + * Set the list of configuration providers + * + * @param containerProviders + */ + public void setContainerProviders(List containerProviders) { + providerLock.lock(); + try { + this.containerProviders = new CopyOnWriteArrayList(containerProviders); + } finally { + providerLock.unlock(); + } + } + + /** + * adds a configuration provider to the List of ConfigurationProviders. a given ConfigurationProvider may be added + * more than once + * + * @param provider the ConfigurationProvider to register + * @deprecated Since 2.1, use {@link #addContainerProvider()} + */ + @Deprecated public void addConfigurationProvider(ConfigurationProvider provider) { + addContainerProvider(provider); + } + + /** + * adds a configuration provider to the List of ConfigurationProviders. a given ConfigurationProvider may be added + * more than once + * + * @param provider the ConfigurationProvider to register + */ + public void addContainerProvider(ContainerProvider provider) { + if (!containerProviders.contains(provider)) { + containerProviders.add(provider); + } + } + + /** + * clears the registered ConfigurationProviders. this method will call destroy() on each of the registered + * ConfigurationProviders + * + * @see com.opensymphony.xwork2.config.ConfigurationProvider#destroy + * @deprecated Since 2.1, use {@link #clearContainerProviders()} + */ + @Deprecated public void clearConfigurationProviders() { + clearContainerProviders(); + } + + public void clearContainerProviders() { + for (ContainerProvider containerProvider : containerProviders) { + try { + containerProvider.destroy(); + } + catch(Exception e) { + LOG.warn("error while destroying container provider ["+containerProvider+"]", e); + } + } + containerProviders.clear(); + } + + /** + * Destroy its managing Configuration instance + */ + public synchronized void destroyConfiguration() { + clearConfigurationProviders(); // let's destroy the ConfigurationProvider first + containerProviders = new CopyOnWriteArrayList(); + if (configuration != null) + configuration.destroy(); // let's destroy it first, before nulling it. + configuration = null; + } + + + /** + * Reloads the Configuration files if the configuration files indicate that they need to be reloaded. + */ + public synchronized void conditionalReload() { + if (FileManager.isReloadingConfigs()) { + boolean reload; + + if (LOG.isDebugEnabled()) { + LOG.debug("Checking ConfigurationProviders for reload."); + } + + reload = false; + + List providers = getContainerProviders(); + for (ContainerProvider provider : providers) { + if (provider.needsReload()) { + if (LOG.isInfoEnabled()) { + LOG.info("Detected container provider "+provider+" needs to be reloaded. Reloading all providers."); + } + reload = true; + + //break; + } + } + + if (packageProviders != null && reload) { + for (PackageProvider provider : packageProviders) { + if (provider.needsReload()) { + if (LOG.isInfoEnabled()) { + LOG.info("Detected package provider "+provider+" needs to be reloaded. Reloading all providers."); + } + reload = true; + + //break; + } + } + } + + if (reload) { + for (ContainerProvider containerProvider : containerProviders) { + try { + containerProvider.destroy(); + } + catch(Exception e) { + LOG.warn("error while destroying configuration provider ["+containerProvider+"]", e); + } + } + packageProviders = configuration.reloadContainer(providers); + } + } + } + + public synchronized void reload() { + packageProviders = getConfiguration().reloadContainer(getContainerProviders()); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationProvider.java new file mode 100644 index 000000000..146532bf5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationProvider.java @@ -0,0 +1,22 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config; + +/** + * Interface to be implemented by all forms of XWork configuration classes. + */ +public interface ConfigurationProvider extends ContainerProvider, PackageProvider { +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java new file mode 100644 index 000000000..679f6d86c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config; + +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; + + +/** + * ConfigurationUtil + * + * @author Jason Carreira + * Created May 23, 2003 11:22:49 PM + */ +public class ConfigurationUtil { + + private static final Logger LOG = LoggerFactory.getLogger(ConfigurationUtil.class); + + + private ConfigurationUtil() { + } + + + public static List buildParentsFromString(Configuration configuration, String parent) { + if ((parent == null) || ("".equals(parent))) { + return Collections.emptyList(); + } + + StringTokenizer tokenizer = new StringTokenizer(parent, ", "); + List parents = new ArrayList(); + + while (tokenizer.hasMoreTokens()) { + String parentName = tokenizer.nextToken().trim(); + + if (!"".equals(parentName)) { + PackageConfig parentPackageContext = configuration.getPackageConfig(parentName); + + if (parentPackageContext != null) { + parents.add(parentPackageContext); + } + } + } + + return parents; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/ContainerProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ContainerProvider.java new file mode 100644 index 000000000..de943f0a1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/ContainerProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config; + +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.location.LocatableProperties; + + +/** + * Provides beans and constants/properties for the Container + * + * @since 2.1 + */ +public interface ContainerProvider { + + /** + * Called before removed from the configuration manager + */ + public void destroy(); + + /** + * Initializes with the configuration + * @param configuration The configuration + * @throws ConfigurationException If anything goes wrong + */ + public void init(Configuration configuration) throws ConfigurationException; + + /** + * Tells whether the ContainerProvider should reload its configuration + * + * @return true, whether the ContainerProvider should reload its configuration, 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 + *

+     *                 ActionConfig config = (ActionConfig)((Map)getActionConfigs.get(namespace)).get(name);
+     *                 
+ * should return a valid config for valid namespace/name pairs + */ + Map> getActionConfigs(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ActionConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ActionConfig.java new file mode 100644 index 000000000..94f0b0ee7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/entities/ActionConfig.java @@ -0,0 +1,339 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.config.entities; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; + +import java.io.Serializable; +import java.util.*; + +import org.apache.commons.lang.StringUtils; + + +/** + * Contains everything needed to configure and execute an action: + *
    + *
  • methodName - the method name to execute on the action. If this is null, the Action will be cast to the Action + * Interface and the execute() method called
  • + *
  • clazz - the class name for the action
  • + *
  • params - the params to be set for this action just before execution
  • + *
  • results - the result map {String -> View class}
  • + *
  • resultParameters - params for results {String -> Map}
  • + *
  • typeConverter - the Ognl TypeConverter to use when getting/setting properties
  • + *
+ * + * @author Mike + * @author Rainer Hermanns + * @version $Revision$ + */ +public class ActionConfig extends Located implements Serializable { + + public static final String WILDCARD = "*"; + + protected List interceptors; // a list of interceptorMapping Objects eg. List + protected Map params; + protected Map results; + protected List exceptionMappings; + protected String className; + protected String methodName; + protected String packageName; + protected String name; + protected Set allowedMethods; + + protected ActionConfig(String packageName, String name, String className) { + this.packageName = packageName; + this.name = name; + this.className = className; + params = new LinkedHashMap(); + results = new LinkedHashMap(); + interceptors = new ArrayList(); + exceptionMappings = new ArrayList(); + allowedMethods = new HashSet(); + allowedMethods.add(WILDCARD); + } + + /** + * Clones an ActionConfig, copying data into new maps and lists + * @param orig The ActionConfig to clone + * @Since 2.1 + */ + protected ActionConfig(ActionConfig orig) { + this.name = orig.name; + this.className = orig.className; + this.methodName = orig.methodName; + this.packageName = orig.packageName; + this.params = new LinkedHashMap(orig.params); + this.interceptors = new ArrayList(orig.interceptors); + this.results = new LinkedHashMap(orig.results); + this.exceptionMappings = new ArrayList(orig.exceptionMappings); + this.allowedMethods = new HashSet(orig.allowedMethods); + } + + public String getName() { + return name; + } + + public String getClassName() { + return className; + } + + public List getExceptionMappings() { + return exceptionMappings; + } + + public List getInterceptors() { + return interceptors; + } + + public Set getAllowedMethods() { + return allowedMethods; + } + + /** + * Returns name of the action method + * + * @return name of the method to execute + */ + public String getMethodName() { + return methodName; + } + + /** + * @return Returns the packageName. + */ + public String getPackageName() { + return packageName; + } + + public Map getParams() { + return params; + } + + public Map getResults() { + return results; + } + + public boolean isAllowedMethod(String method) { + if (allowedMethods.size() == 1 && WILDCARD.equals(allowedMethods.iterator().next())) { + return true; + } else { + return allowedMethods.contains(method); + } + } + + @Override public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof ActionConfig)) { + return false; + } + + final ActionConfig actionConfig = (ActionConfig) o; + + if ((className != null) ? (!className.equals(actionConfig.className)) : (actionConfig.className != null)) { + return false; + } + + if ((name != null) ? (!name.equals(actionConfig.name)) : (actionConfig.name != null)) { + return false; + } + + if ((interceptors != null) ? (!interceptors.equals(actionConfig.interceptors)) : (actionConfig.interceptors != null)) + { + return false; + } + + if ((methodName != null) ? (!methodName.equals(actionConfig.methodName)) : (actionConfig.methodName != null)) { + return false; + } + + if ((params != null) ? (!params.equals(actionConfig.params)) : (actionConfig.params != null)) { + return false; + } + + if ((results != null) ? (!results.equals(actionConfig.results)) : (actionConfig.results != null)) { + return false; + } + + if ((allowedMethods != null) ? (!allowedMethods.equals(actionConfig.allowedMethods)) : (actionConfig.allowedMethods != null)) { + return false; + } + + return true; + } + + + @Override public int hashCode() { + int result; + result = (interceptors != null ? interceptors.hashCode() : 0); + result = 31 * result + (params != null ? params.hashCode() : 0); + result = 31 * result + (results != null ? results.hashCode() : 0); + result = 31 * result + (exceptionMappings != null ? exceptionMappings.hashCode() : 0); + result = 31 * result + (className != null ? className.hashCode() : 0); + result = 31 * result + (methodName != null ? methodName.hashCode() : 0); + result = 31 * result + (packageName != null ? packageName.hashCode() : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (allowedMethods != null ? allowedMethods.hashCode() : 0); + return result; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{ActionConfig "); + sb.append(name).append(" ("); + sb.append(className); + if (methodName != null) { + sb.append(".").append(methodName).append("()"); + } + sb.append(")"); + sb.append(" - ").append(location); + sb.append("}"); + return sb.toString(); + } + + /** + * The builder for this object. An instance of this object is the only way to construct a new instance. The + * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining. + * After setting any values you need, call the {@link #build()} method to create the object. + */ + public static class Builder implements InterceptorListHolder{ + + private ActionConfig target; + + public Builder(ActionConfig toClone) { + target = new ActionConfig(toClone); + } + + public Builder(String packageName, String name, String className) { + target = new ActionConfig(packageName, name, className); + } + + public Builder packageName(String name) { + target.packageName = name; + return this; + } + + public Builder name(String name) { + target.name = name; + return this; + } + + public Builder className(String name) { + target.className = name; + return this; + } + + public Builder defaultClassName(String name) { + if (StringUtils.isEmpty(target.className)) { + target.className = name; + } + return this; + } + + public Builder methodName(String method) { + target.methodName = method; + return this; + } + + public Builder addExceptionMapping(ExceptionMappingConfig exceptionMapping) { + target.exceptionMappings.add(exceptionMapping); + return this; + } + + public Builder addExceptionMappings(Collection mappings) { + target.exceptionMappings.addAll(mappings); + return this; + } + + public Builder exceptionMappings(Collection 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: +

+
+    ResultConfig config = new ResultConfig.Builder("success", "myapp.MyResult")
+        .addParam("location", "/foo.jsp")
+        .build();
+
+ 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 { + /** + *

The logging instance

+ */ + private static final Logger log = LoggerFactory.getLogger(AbstractMatcher.class); + + /** + *

Handles all wildcard pattern matching.

+ */ + PatternMatcher wildcard; + + /** + *

The compiled patterns and their associated target objects

+ */ + List> compiledPatterns = new ArrayList>();; + + public AbstractMatcher(PatternMatcher helper) { + this.wildcard = (PatternMatcher) helper; + } + + /** + *

+ * 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.

+ */ + private static class Mapping implements Serializable { + /** + *

The original pattern.

+ */ + private String original; + + + /** + *

The compiled pattern.

+ */ + private Object pattern; + + /** + *

The original object.

+ */ + private E config; + + /** + *

Contructs a read-only Mapping instance.

+ * + * @param original The original pattern + * @param pattern The compiled pattern + * @param config The original object + */ + public Mapping(String original, Object pattern, E config) { + this.original = original; + this.pattern = pattern; + this.config = config; + } + + /** + *

Gets the compiled wildcard pattern.

+ * + * @return The compiled pattern + */ + public Object getPattern() { + return this.pattern; + } + + /** + *

Gets the object that contains the pattern.

+ * + * @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 exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext); + packageContext.addGlobalExceptionMappingConfigs(exceptionMappings); + } + } + + // protected void loadIncludes(Element rootElement, DocumentBuilder db) throws Exception { + // NodeList includeList = rootElement.getElementsByTagName("include"); + // + // for (int i = 0; i < includeList.getLength(); i++) { + // Element includeElement = (Element) includeList.item(i); + // String fileName = includeElement.getAttribute("file"); + // includedFileNames.add(fileName); + // loadConfigurationFile(fileName, db); + // } + // } + protected InterceptorStackConfig loadInterceptorStack(Element element, PackageConfig.Builder context) throws ConfigurationException { + String name = element.getAttribute("name"); + + InterceptorStackConfig.Builder config = new InterceptorStackConfig.Builder(name) + .location(DomHelper.getLocationObject(element)); + NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref"); + + for (int j = 0; j < interceptorRefList.getLength(); j++) { + Element interceptorRefElement = (Element) interceptorRefList.item(j); + List interceptors = lookupInterceptorReference(context, interceptorRefElement); + config.addInterceptors(interceptors); + } + + return config.build(); + } + + protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException { + NodeList interceptorStackList = element.getElementsByTagName("interceptor-stack"); + + for (int i = 0; i < interceptorStackList.getLength(); i++) { + Element interceptorStackElement = (Element) interceptorStackList.item(i); + + InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context); + + context.addInterceptorStackConfig(config); + } + } + + protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException { + NodeList interceptorList = element.getElementsByTagName("interceptor"); + + for (int i = 0; i < interceptorList.getLength(); i++) { + Element interceptorElement = (Element) interceptorList.item(i); + String name = interceptorElement.getAttribute("name"); + String className = interceptorElement.getAttribute("class"); + + Map params = XmlHelper.getParams(interceptorElement); + InterceptorConfig config = new InterceptorConfig.Builder(name, className) + .addParams(params) + .location(DomHelper.getLocationObject(interceptorElement)) + .build(); + + context.addInterceptorConfig(config); + } + + loadInterceptorStacks(element, context); + } + + // protected void loadPackages(Element rootElement) throws ConfigurationException { + // NodeList packageList = rootElement.getElementsByTagName("package"); + // + // for (int i = 0; i < packageList.getLength(); i++) { + // Element packageElement = (Element) packageList.item(i); + // addPackage(packageElement); + // } + // } + private List loadConfigurationFiles(String fileName, Element includeElement) { + List docs = new ArrayList(); + List finalDocs = new ArrayList(); + if (!includedFileNames.contains(fileName)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Loading action configurations from: " + fileName); + } + + includedFileNames.add(fileName); + + Iterator urls = null; + InputStream is = null; + + IOException ioException = null; + try { + urls = getConfigurationUrls(fileName); + } catch (IOException ex) { + ioException = ex; + } + + if (urls == null || !urls.hasNext()) { + if (errorIfMissing) { + throw new ConfigurationException("Could not open files of the name " + fileName, ioException); + } else { + LOG.info("Unable to locate configuration files of the name " + + fileName + ", skipping"); + return docs; + } + } + + URL url = null; + while (urls.hasNext()) { + try { + url = urls.next(); + is = FileManager.loadFile(url); + + InputSource in = new InputSource(is); + + in.setSystemId(url.toString()); + + docs.add(DomHelper.parse(in, dtdMappings)); + } catch (XWorkException e) { + if (includeElement != null) { + throw new ConfigurationException("Unable to load " + url, e, includeElement); + } else { + throw new ConfigurationException("Unable to load " + url, e); + } + } catch (Exception e) { + final String s = "Caught exception while loading file " + fileName; + throw new ConfigurationException(s, e, includeElement); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOG.error("Unable to close input stream", e); + } + } + } + } + + //sort the documents, according to the "order" attribute + Collections.sort(docs, new Comparator() { + public int compare(Document doc1, Document doc2) { + return XmlHelper.getLoadOrder(doc1).compareTo(XmlHelper.getLoadOrder(doc2)); + } + }); + + for (Document doc : docs) { + 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 ("include".equals(nodeName)) { + String includeFileName = child.getAttribute("file"); + if (includeFileName.indexOf('*') != -1) { + // handleWildCardIncludes(includeFileName, docs, child); + ClassPathFinder wildcardFinder = new ClassPathFinder(); + wildcardFinder.setPattern(includeFileName); + Vector wildcardMatches = wildcardFinder.findMatches(); + for (String match : wildcardMatches) { + finalDocs.addAll(loadConfigurationFiles(match, child)); + } + } else { + finalDocs.addAll(loadConfigurationFiles(includeFileName, child)); + } + } + } + } + finalDocs.add(doc); + loadedFileUrls.add(url.toString()); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Loaded action configuration from: " + fileName); + } + } + return finalDocs; + } + + protected Iterator getConfigurationUrls(String fileName) throws IOException { + return ClassLoaderUtil.getResources(fileName, XmlConfigurationProvider.class, false); + } + + /** + * Allows subclasses to load extra information from the document + * + * @param doc The configuration document + */ + protected void loadExtraConfiguration(Document doc) { + // no op + } + + /** + * Looks up the Interceptor Class from the interceptor-ref name and creates an instance, which is added to the + * provided List, or, if this is a ref to a stack, it adds the Interceptor instances from the List to this stack. + * + * @param interceptorRefElement Element to pull interceptor ref data from + * @param context The PackageConfig to lookup the interceptor from + * @return A list of Interceptor objects + */ + private List lookupInterceptorReference(PackageConfig.Builder context, Element interceptorRefElement) throws ConfigurationException { + String refName = interceptorRefElement.getAttribute("name"); + Map refParams = XmlHelper.getParams(interceptorRefElement); + + Location loc = LocationUtils.getLocation(interceptorRefElement); + return InterceptorBuilder.constructInterceptorReference(context, refName, refParams, loc, objectFactory); + } + + List getDocuments() { + return documents; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java new file mode 100644 index 000000000..7dd24e6f4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java @@ -0,0 +1,128 @@ +/* + * 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 org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Document; +import org.apache.commons.lang.StringUtils; + +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * XML utilities. + * + * @author Mike + */ +public class XmlHelper { + + + /** + * This method will find all the parameters under this paramsElement and return them as + * Map. For example, + *

+     *   
+     *      value1
+     *      value2
+     *      value3
+     *   
+     * 
+ * will returns a Map with the following key, value pairs :- + *
    + *
  • param1 - value1
  • + *
  • param2 - value2
  • + *
  • param3 - value3
  • + *
+ * + * @param paramsElement + * @return + */ + public static Map getParams(Element paramsElement) { + LinkedHashMap params = new LinkedHashMap(); + + if (paramsElement == null) { + return params; + } + + NodeList childNodes = paramsElement.getChildNodes(); + + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + + if ((childNode.getNodeType() == Node.ELEMENT_NODE) && "param".equals(childNode.getNodeName())) { + Element paramElement = (Element) childNode; + String paramName = paramElement.getAttribute("name"); + + String val = getContent(paramElement); + if (val.length() > 0) { + params.put(paramName, val); + } + } + } + + return params; + } + + /** + * This method will return the content of this particular element. + * For example, + *

+ *

+     *    something_1
+     * 
+ * When the {@link org.w3c.dom.Element} <result> is passed in as + * argument (element to this method, it returns the content of it, + * namely, something_1 in the example above. + * + * @return + */ + public static String getContent(Element element) { + StringBuilder paramValue = new StringBuilder(); + NodeList childNodes = element.getChildNodes(); + for (int j = 0; j < childNodes.getLength(); j++) { + Node currentNode = childNodes.item(j); + if (currentNode != null && + currentNode.getNodeType() == Node.TEXT_NODE) { + String val = currentNode.getNodeValue(); + if (val != null) { + paramValue.append(val.trim()); + } + } + } + return paramValue.toString().trim(); + } + + /** + * Return the value of the "order" attribute from the root element + */ + public static Integer getLoadOrder(Document doc) { + Element rootElement = doc.getDocumentElement(); + String number = rootElement.getAttribute("order"); + if (StringUtils.isNotBlank(number)) { + try { + return Integer.parseInt(number); + } catch (NumberFormatException e) { + return Integer.MAX_VALUE; + } + } else { + //no order specified + return Integer.MAX_VALUE; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/package.html new file mode 100644 index 000000000..946fc4ef8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/package.html @@ -0,0 +1 @@ +Configuration provider classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java new file mode 100644 index 000000000..86d71f0d2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java @@ -0,0 +1,54 @@ +//-------------------------------------------------------------------------- +//Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard +//All rights reserved. +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are +//met: +// +//Redistributions of source code must retain the above copyright notice, +//this list of conditions and the following disclaimer. +//Redistributions in binary form must reproduce the above copyright +//notice, this list of conditions and the following disclaimer in the +//documentation and/or other materials provided with the distribution. +//Neither the name of the Drew Davidson nor the names of its contributors +//may be used to endorse or promote products derived from this software +//without specific prior written permission. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +//COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +//OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +//AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +//OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +//THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +//DAMAGE. +//-------------------------------------------------------------------------- +package com.opensymphony.xwork2.conversion; + +import java.util.Map; + +/** +* Interface for handling null results from Chains. +* Object has the opportunity to substitute an object for the +* null and continue. +* @author Luke Blanshard (blanshlu@netscape.net) +* @author Drew Davidson (drew@ognl.org) +*/ +public interface NullHandler +{ + /** + Method called on target returned null. + */ + public Object nullMethodResult(Map context, Object target, String methodName, Object[] args); + + /** + Property in target evaluated to null. Property can be a constant + String property name or a DynamicSubscript. + */ + public Object nullPropertyValue(Map context, Object target, Object property); +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java new file mode 100644 index 000000000..fe2b74840 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java @@ -0,0 +1,36 @@ +/* + * 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.conversion; + +/** + * Determines what the key and and element class of a Map or Collection should be. For Maps, the elements are the + * values. For Collections, the elements are the elements of the collection. + *

+ * See the implementations for javadoc description for the methods as they are dependent on the concrete implementation. + * + * @author Gabriel Zimmerman + */ +public interface ObjectTypeDeterminer { + + public Class getKeyClass(Class parentClass, String property); + + public Class getElementClass(Class parentClass, String property, Object key); + + public String getKeyProperty(Class parentClass, String property); + + public boolean shouldCreateIfNew(Class parentClass, String property, Object target, String keyProperty, boolean isIndexAccessed); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java new file mode 100644 index 000000000..033ee7337 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java @@ -0,0 +1,61 @@ +/* + * 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.conversion; + +import com.opensymphony.xwork2.XWorkException; + + +/** + * TypeConversionException should be thrown by any TypeConverters which fail to convert values + * + * @author Jason Carreira + * Created Oct 3, 2003 12:18:33 AM + */ +public class TypeConversionException extends XWorkException { + + /** + * Constructs a XWorkException with no detail message. + */ + public TypeConversionException() { + } + + /** + * Constructs a XWorkException with the specified + * detail message. + * + * @param s the detail message. + */ + public TypeConversionException(String s) { + super(s); + } + + /** + * Constructs a XWorkException with no detail message. + */ + public TypeConversionException(Throwable cause) { + super(cause); + } + + /** + * Constructs a XWorkException with the specified + * detail message. + * + * @param s the detail message. + */ + public TypeConversionException(String s, Throwable cause) { + super(s, cause); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java new file mode 100644 index 000000000..0fb67d78a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java @@ -0,0 +1,64 @@ +//-------------------------------------------------------------------------- +// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// Neither the name of the Drew Davidson nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +//-------------------------------------------------------------------------- +package com.opensymphony.xwork2.conversion; + +import java.lang.reflect.Member; +import java.util.Map; + +/** + * Interface for accessing the type conversion facilities within a context. + * + * This interface was copied from OGNL's TypeConverter + * + * @author Luke Blanshard (blanshlu@netscape.net) + * @author Drew Davidson (drew@ognl.org) + */ +public interface TypeConverter +{ + /** + * Converts the given value to a given type. The OGNL context, target, member and + * name of property being set are given. This method should be able to handle + * conversion in general without any context, target, member or property name specified. + * @param context context under which the conversion is being done + * @param target target object in which the property is being set + * @param member member (Constructor, Method or Field) being set + * @param propertyName property name being set + * @param value value to be converted + * @param toType type to which value is converted + * @return Converted value of type toType or TypeConverter.NoConversionPossible to indicate that the + conversion was not possible. + */ + public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType); + + public static final Object NO_CONVERSION_POSSIBLE = "ognl.NoConversionPossible"; + + public static final String TYPE_CONVERTER_CONTEXT_KEY = "_typeConverter"; +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java new file mode 100644 index 000000000..90d2ab848 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.conversion.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

A marker annotation for type conversions at Type level. + * + * + *

Annotation usage: + * + * + *

The Conversion annotation must be applied at Type level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterRequiredDefaultDescription
conversionno used for Type Conversions applied at Type level.
+ * + * + *

Example code: + * + *

+ * 
+ * @Conversion()
+ * public class ConversionAction implements Action {
+ * }
+ *
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Conversion { + + /** + * Allow Type Conversions being applied at Type level. + */ + TypeConversion[] conversions() default {}; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java new file mode 100644 index 000000000..e30bea48a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.conversion.annotations; + +/** + * ConversionRule + * + * @author Rainer Hermanns + * @version $Id$ + */ +public enum ConversionRule { + + PROPERTY, COLLECTION, MAP, KEY, KEY_PROPERTY, ELEMENT, CREATE_IF_NULL; + + @Override + public String toString() { + return super.toString().toUpperCase(); + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java new file mode 100644 index 000000000..d80a92686 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.conversion.annotations; + +/** + * ConversionType + * + * @author Rainer Hermanns + * @version $Id$ + */ +public enum ConversionType { + + + APPLICATION, CLASS; + + @Override + public String toString() { + return super.toString().toUpperCase(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java new file mode 100644 index 000000000..a857d49f0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java @@ -0,0 +1,178 @@ +/* + * 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.conversion.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

This annotation is used for class and application wide conversion rules. + *

+ * Class wide conversion:
+ * The conversion rules will be assembled in a file called XXXAction-conversion.properties + * within the same package as the related action class. + * Set type to: type = ConversionType.CLASS + *

+ *

+ * Allication wide conversion:
+ * The conversion rules will be assembled within the xwork-conversion.properties file within the classpath root. + * Set type to: type = ConversionType.APPLICATION + *

+ * + * + *

Annotation usage: + * + * + * The TypeConversion annotation can be applied at property and method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterRequiredDefaultDescription
keynoThe annotated property/key nameThe optional property name mostly used within TYPE level annotations.
typenoConversionType.CLASSEnum value of ConversionType. Determines whether the conversion should be applied at application or class level.
rulenoConversionRule.PROPERTYEnum value of ConversionRule. The ConversionRule can be a property, a Collection or a Map.
convertereither this or value The class name of the TypeConverter to be used as converter.
valueeither converter or this The value to set for ConversionRule.KEY_PROPERTY.
+ * + * + * + *

Example code: + * + *

+ * 
+ * @Conversion()
+ * public class ConversionAction implements Action {
+ *
+ *   private String convertInt;
+ *
+ *   private String convertDouble;
+ *   private List users = null;
+ *
+ *   private HashMap keyValues = null;
+ *
+ *   @TypeConversion(type = ConversionType.APPLICATION, converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+ *   public void setConvertInt( String convertInt ) {
+ *       this.convertInt = convertInt;
+ *   }
+ *
+ *   @TypeConversion(converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+ *   public void setConvertDouble( String convertDouble ) {
+ *       this.convertDouble = convertDouble;
+ *   }
+ *
+ *   @TypeConversion(rule = ConversionRule.COLLECTION, converter = "java.util.String")
+ *   public void setUsers( List users ) {
+ *       this.users = users;
+ *   }
+ *
+ *   @TypeConversion(rule = ConversionRule.MAP, converter = "java.math.BigInteger")
+ *   public void setKeyValues( HashMap keyValues ) {
+ *       this.keyValues = keyValues;
+ *   }
+ *
+ *   @TypeConversion(type = ConversionType.APPLICATION, property = "java.util.Date", converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+ *   public String execute() throws Exception {
+ *       return SUCCESS;
+ *   }
+ * }
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TypeConversion { + + /** + * The optional key name used within TYPE level annotations. + * Defaults to the property name. + */ + String key() default ""; + + /** + * The ConversionType can be either APPLICATION or CLASS. + * Defaults to CLASS. + * + * Note: If you use ConversionType.APPLICATION, you can not set a value! + */ + ConversionType type() default ConversionType.CLASS; + + /** + * The ConversionRule can be a PROPERTY, KEY, KEY_PROPERTY, ELEMENT, COLLECTION (deprecated) or a MAP. + * Note: Collection and Map vonversion rules can be determined via com.opensymphony.xwork2.util.DefaultObjectTypeDeterminer. + * + * @see com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer + */ + ConversionRule rule() default ConversionRule.PROPERTY; + + /** + * The class of the TypeConverter to be used as converter. + * + * Note: This can not be used with ConversionRule.KEY_PROPERTY! + */ + String converter() default ""; + + /** + * If used with ConversionRule.KEY_PROPERTY specify a value here! + * + * Note: If you use ConversionType.APPLICATION, you can not set a value! + */ + String value() default ""; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html new file mode 100644 index 000000000..e2a91d0a1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html @@ -0,0 +1 @@ +Type conversion annotations. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java new file mode 100644 index 000000000..988e4634e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java @@ -0,0 +1,91 @@ +/* + * 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.conversion.impl; + +/** + * + *

+ * Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web + * is type-agnostic (everything is a string in HTTP), XWork's type conversion features are very useful. For instance, + * if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have + * XWork do the conversion both from String to Point and from Point to String. + *

+ *

Using this "point" example, if your action (or another compound object in which you are setting properties on) + * has a corresponding ClassName-conversion.properties file, XWork will use the configured type converters for + * conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following + * entry to ClassName-conversion.properties (Note that the PointConverter should impl the TypeConverter + * interface): + *

+ *

point = com.acme.PointConverter + *

+ *

Your type converter should be sure to check what class type it is being requested to convert. Because it is used + * for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in + * to Points, and one that turns Points in to Strings. + *

+ *

After this is done, you can now reference your point (using <ww:property value="post"/> in JSP or ${point} + * in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be + * converted back to a Point once again. + *

+ *

In some situations you may wish to apply a type converter globally. This can be done by editing the file + * xwork-conversion.properties in the root of your class path (typically WEB-INF/classes) and providing a + * property in the form of the class name of the object you wish to convert on the left hand side and the class name of + * the type converter on the right hand side. For example, providing a type converter for all Point objects would mean + * adding the following entry: + *

+ *

com.acme.Point = com.acme.PointConverter + *

+ * + *

+ *

+ *

+ * + *

+ * Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out + * properly formatted dates. Rather, you should use the i18n features of XWork (and consult the JavaDocs for JDK's + * MessageFormat object) to see how a properly formatted date should be displayed. + *

+ * + *

+ *

+ *

+ * + *

+ * Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the + * input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string, + * "", cannot be converted to a number might not be important - especially in a web environment where it is hard to + * distinguish between a user not entering a value vs. entering a blank value. + *

+ *

By default, all conversion errors are reported using the generic i18n key xwork.default.invalid.fieldvalue, + * which you can override (the default text is Invalid field value for field "xxx", where xxx is the field name) + * in your global i18n resource bundle. + *

+ *

However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n + * key associated with just your action (Action.properties) using the pattern invalid.fieldvalue.xxx, where xxx + * is the field name. + *

+ *

It is important to know that none of these errors are actually reported directly. Rather, they are added to a map + * called conversionErrors in the ActionContext. There are several ways this map can then be accessed and the + * errors can be reported accordingly. + *

+ * + * + * @author Pat Lightbody + * @author Rainer Hermanns + * @see com.opensymphony.xwork2.conversion.impl.XWorkConverter + * @deprecated Since XWork 2.0.4, the implementation of XWorkConverter handles the processing of annotations. + */ +@Deprecated public class AnnotationXWorkConverter extends XWorkConverter { +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java new file mode 100644 index 000000000..d4b7e0010 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java @@ -0,0 +1,360 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.CreateIfNull; +import com.opensymphony.xwork2.util.Element; +import com.opensymphony.xwork2.util.Key; +import com.opensymphony.xwork2.util.KeyProperty; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + +import java.beans.IntrospectionException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * + * + * This {@link ObjectTypeDeterminer} looks at the Class-conversion.properties for entries that indicated what + * objects are contained within Maps and Collections. For Collections, such as Lists, the element is specified using the + * pattern Element_xxx, where xxx is the field name of the collection property in your action or object. For + * Maps, both the key and the value may be specified by using the pattern Key_xxx and Element_xxx, + * respectively. + * + *

From WebWork 2.1.x, the Collection_xxx format is still supported and honored, although it is deprecated + * and will be removed eventually. + * + * + * + * + * @author Gabriel Zimmerman + */ +public class DefaultObjectTypeDeterminer implements ObjectTypeDeterminer { + + + protected static final Logger LOG = LoggerFactory.getLogger(DefaultObjectTypeDeterminer.class); + + public static final String KEY_PREFIX = "Key_"; + public static final String ELEMENT_PREFIX = "Element_"; + public static final String KEY_PROPERTY_PREFIX = "KeyProperty_"; + public static final String CREATE_IF_NULL_PREFIX = "CreateIfNull_"; + public static final String DEPRECATED_ELEMENT_PREFIX = "Collection_"; + + private ReflectionProvider reflectionProvider; + private XWorkConverter xworkConverter; + + @Inject + public DefaultObjectTypeDeterminer(@Inject XWorkConverter conv, @Inject ReflectionProvider prov) { + this.reflectionProvider = prov; + this.xworkConverter = conv; + + } + + /** + * Determines the key class by looking for the value of @Key annotation for the given class. + * If no annotation is found, the key class is determined by using the generic parametrics. + * + * As fallback, it determines the key class by looking for the value of Key_${property} in the properties + * file for the given class. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @see com.opensymphony.xwork2.conversion.ObjectTypeDeterminer#getKeyClass(Class, String) + */ + public Class getKeyClass(Class parentClass, String property) { + Key annotation = getAnnotation(parentClass, property, Key.class); + + if (annotation != null) { + return annotation.value(); + } + + Class clazz = getClass(parentClass, property, false); + + if (clazz != null) { + return clazz; + } + + return (Class) xworkConverter.getConverter(parentClass, KEY_PREFIX + property); + } + + + /** + * Determines the element class by looking for the value of @Element annotation for the given + * class. + * If no annotation is found, the element class is determined by using the generic parametrics. + * + * As fallback, it determines the key class by looking for the value of Element_${property} in the properties + * file for the given class. Also looks for the deprecated Collection_${property} + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @see com.opensymphony.xwork2.conversion.ObjectTypeDeterminer#getElementClass(Class, String, Object) + */ + public Class getElementClass(Class parentClass, String property, Object key) { + Element annotation = getAnnotation(parentClass, property, Element.class); + + if (annotation != null) { + return annotation.value(); + } + + Class clazz = getClass(parentClass, property, true); + + if (clazz != null) { + return clazz; + } + + clazz = (Class) xworkConverter.getConverter(parentClass, ELEMENT_PREFIX + property); + + if (clazz == null) { + clazz = (Class) xworkConverter + .getConverter(parentClass, DEPRECATED_ELEMENT_PREFIX + property); + + if (clazz != null) { + LOG.info("The Collection_xxx pattern for collection type conversion is deprecated. Please use Element_xxx!"); + } + } + return clazz; + + } + + + /** + * Determines the key property for a Collection by getting it from the @KeyProperty annotation. + * + * As fallback, it determines the String key property for a Collection by getting it from the conversion properties + * file using the KeyProperty_ prefix. KeyProperty_${property}=somePropertyOfBeansInTheSet + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @see com.opensymphony.xwork2.conversion.ObjectTypeDeterminer#getKeyProperty(Class, String) + */ + public String getKeyProperty(Class parentClass, String property) { + KeyProperty annotation = getAnnotation(parentClass, property, KeyProperty.class); + + if (annotation != null) { + return annotation.value(); + } + + return (String) xworkConverter.getConverter(parentClass, KEY_PROPERTY_PREFIX + property); + } + + + /** + * Determines the createIfNull property for a Collection or Map by getting it from the @CreateIfNull annotation. + * + * As fallback, it determines the boolean CreateIfNull property for a Collection or Map by getting it from the + * conversion properties file using the CreateIfNull_ prefix. CreateIfNull_${property}=true|false + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param target the target object + * @param keyProperty the keyProperty value + * @param isIndexAccessed true, if the collection or map is accessed via index, false otherwise. + * @return true, if the Collection or Map should be created, false otherwise. + * @see ObjectTypeDeterminer#getKeyProperty(Class, String) + */ + public boolean shouldCreateIfNew(Class parentClass, + String property, + Object target, + String keyProperty, + boolean isIndexAccessed) { + + CreateIfNull annotation = getAnnotation(parentClass, property, CreateIfNull.class); + + if (annotation != null) { + return annotation.value(); + } + + String configValue = (String) xworkConverter.getConverter(parentClass, CREATE_IF_NULL_PREFIX + property); + //check if a value is in the config + if (configValue!=null) { + if ("true".equalsIgnoreCase(configValue)) { + return true; + } + if ("false".equalsIgnoreCase(configValue)) { + return false; + } + } + + //default values depend on target type + //and whether this is accessed by an index + //in the case of List + if ((target instanceof Map) || isIndexAccessed) { + return true; + } else { + return false; + } + + } + + /** + * Retrieves an annotation for the specified property of field, setter or getter. + * + * @param the annotation type to be retrieved + * @param parentClass the class + * @param property the property + * @param annotationClass the annotation + * @return the field or setter/getter annotation or null if not found + */ + protected T getAnnotation(Class parentClass, String property, Class annotationClass) { + T annotation = null; + Field field = reflectionProvider.getField(parentClass, property); + + if (field != null) { + annotation = field.getAnnotation(annotationClass); + } + if (annotation == null) { // HINT: try with setter + annotation = getAnnotationFromSetter(parentClass, property, annotationClass); + } + if (annotation == null) { // HINT: try with getter + annotation = getAnnotationFromGetter(parentClass, property, annotationClass); + } + + return annotation; + } + + /** + * Retrieves an annotation for the specified field of getter. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param annotationClass The annotation + * @return concrete Annotation instance or null if none could be retrieved. + */ + private T getAnnotationFromGetter(Class parentClass, String property, Class annotationClass) { + try { + Method getter = reflectionProvider.getGetMethod(parentClass, property); + + if (getter != null) { + return getter.getAnnotation(annotationClass); + } + } + catch (ReflectionException ognle) { + ; // ignore + } + catch (IntrospectionException ie) { + ; // ignore + } + return null; + } + + /** + * Retrieves an annotation for the specified field of setter. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param annotationClass The annotation + * @return concrete Annotation instance or null if none could be retrieved. + */ + private T getAnnotationFromSetter(Class parentClass, String property, Class annotationClass) { + try { + Method setter = reflectionProvider.getSetMethod(parentClass, property); + + if (setter != null) { + return setter.getAnnotation(annotationClass); + } + } + catch (ReflectionException ognle) { + ; // ignore + } + catch (IntrospectionException ie) { + ; // ignore + } + return null; + } + + /** + * Returns the class for the given field via generic type check. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param element true for indexed types and Maps. + * @return Class of the specified field. + */ + private Class getClass(Class parentClass, String property, boolean element) { + + + try { + + Field field = reflectionProvider.getField(parentClass, property); + + Type genericType = null; + + // Check fields first + if (field != null) { + genericType = field.getGenericType(); + } + + // Try to get ParameterType from setter method + if (genericType == null || !(genericType instanceof ParameterizedType)) { + try { + Method setter = reflectionProvider.getSetMethod(parentClass, property); + genericType = setter.getGenericParameterTypes()[0]; + } + catch (ReflectionException ognle) { + ; // ignore + } + catch (IntrospectionException ie) { + ; // ignore + } + } + + // Try to get ReturnType from getter method + if (genericType == null || !(genericType instanceof ParameterizedType)) { + try { + Method getter = reflectionProvider.getGetMethod(parentClass, property); + genericType = getter.getGenericReturnType(); + } + catch (ReflectionException ognle) { + ; // ignore + } + catch (IntrospectionException ie) { + ; // ignore + } + } + + if (genericType instanceof ParameterizedType) { + + + ParameterizedType type = (ParameterizedType) genericType; + + int index = (element && type.getRawType().toString().contains(Map.class.getName())) ? 1 : 0; + + Type resultType = type.getActualTypeArguments()[index]; + + if ( resultType instanceof ParameterizedType) { + return (Class) ((ParameterizedType) resultType).getRawType(); + } + return (Class) resultType; + + } + } catch (Exception e) { + if ( LOG.isDebugEnabled()) { + LOG.debug("Error while retrieving generic property class for property=" + property, e); + } + } + return null; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java new file mode 100644 index 000000000..11b55a86d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java @@ -0,0 +1,328 @@ +//-------------------------------------------------------------------------- +// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// Neither the name of the Drew Davidson nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +//-------------------------------------------------------------------------- +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper; + +import java.lang.reflect.Array; +import java.lang.reflect.Member; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Default type conversion. Converts among numeric types and also strings. Contains the basic + * type mapping code from OGNL. + * + * @author Luke Blanshard (blanshlu@netscape.net) + * @author Drew Davidson (drew@ognl.org) + */ +public class DefaultTypeConverter implements TypeConverter { + private static final String NULL_STRING = "null"; + + private final Map primitiveDefaults; + + public DefaultTypeConverter() { + Map map = new HashMap(); + map.put(Boolean.TYPE, Boolean.FALSE); + map.put(Byte.TYPE, Byte.valueOf((byte) 0)); + map.put(Short.TYPE, Short.valueOf((short) 0)); + map.put(Character.TYPE, new Character((char) 0)); + map.put(Integer.TYPE, Integer.valueOf(0)); + map.put(Long.TYPE, Long.valueOf(0L)); + map.put(Float.TYPE, new Float(0.0f)); + map.put(Double.TYPE, new Double(0.0)); + map.put(BigInteger.class, new BigInteger("0")); + map.put(BigDecimal.class, new BigDecimal(0.0)); + primitiveDefaults = Collections.unmodifiableMap(map); + } + + public Object convertValue(Map context, Object value, Class toType) { + return convertValue(value, toType); + } + + public Object convertValue(Map context, Object target, Member member, + String propertyName, Object value, Class toType) { + return convertValue(context, value, toType); + } + + public TypeConverter getTypeConverter( Map context ) + { + Object obj = context.get(TypeConverter.TYPE_CONVERTER_CONTEXT_KEY); + if (obj instanceof TypeConverter) { + return (TypeConverter) obj; + + // for backwards-compatibility + } else if (obj instanceof ognl.TypeConverter) { + return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj); + } + return null; + } + + /** + * Returns the value converted numerically to the given class type + * + * This method also detects when arrays are being converted and converts the + * components of one array to the type of the other. + * + * @param value + * an object to be converted to the given type + * @param toType + * class type to be converted to + * @return converted value of the type given, or value if the value cannot + * be converted to the given type. + */ + public Object convertValue(Object value, Class toType) { + Object result = null; + + if (value != null) { + /* If array -> array then convert components of array individually */ + if (value.getClass().isArray() && toType.isArray()) { + Class componentType = toType.getComponentType(); + + result = Array.newInstance(componentType, Array + .getLength(value)); + for (int i = 0, icount = Array.getLength(value); i < icount; i++) { + Array.set(result, i, convertValue(Array.get(value, i), + componentType)); + } + } else { + if ((toType == Integer.class) || (toType == Integer.TYPE)) + result = Integer.valueOf((int) longValue(value)); + if ((toType == Double.class) || (toType == Double.TYPE)) + result = new Double(doubleValue(value)); + if ((toType == Boolean.class) || (toType == Boolean.TYPE)) + result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE; + if ((toType == Byte.class) || (toType == Byte.TYPE)) + result = Byte.valueOf((byte) longValue(value)); + if ((toType == Character.class) || (toType == Character.TYPE)) + result = new Character((char) longValue(value)); + if ((toType == Short.class) || (toType == Short.TYPE)) + result = Short.valueOf((short) longValue(value)); + if ((toType == Long.class) || (toType == Long.TYPE)) + result = Long.valueOf(longValue(value)); + if ((toType == Float.class) || (toType == Float.TYPE)) + result = new Float(doubleValue(value)); + if (toType == BigInteger.class) + result = bigIntValue(value); + if (toType == BigDecimal.class) + result = bigDecValue(value); + if (toType == String.class) + result = stringValue(value); + if (Enum.class.isAssignableFrom(toType)) + result = enumValue((Class)toType, value); + } + } else { + if (toType.isPrimitive()) { + result = primitiveDefaults.get(toType); + } + } + return result; + } + + /** + * Evaluates the given object as a boolean: if it is a Boolean object, it's + * easy; if it's a Number or a Character, returns true for non-zero objects; + * and otherwise returns true for non-null objects. + * + * @param value + * an object to interpret as a boolean + * @return the boolean value implied by the given object + */ + public static boolean booleanValue(Object value) { + if (value == null) + return false; + Class c = value.getClass(); + if (c == Boolean.class) + return ((Boolean) value).booleanValue(); + // if ( c == String.class ) + // return ((String)value).length() > 0; + if (c == Character.class) + return ((Character) value).charValue() != 0; + if (value instanceof Number) + return ((Number) value).doubleValue() != 0; + return true; // non-null + } + + public Enum enumValue(Class toClass, Object o) { + Enum result = null; + if (o == null) { + result = null; + } else if (o instanceof String[]) { + result = Enum.valueOf(toClass, ((String[]) o)[0]); + } else if (o instanceof String) { + result = Enum.valueOf(toClass, (String) o); + } + return result; + } + + /** + * Evaluates the given object as a long integer. + * + * @param value + * an object to interpret as a long integer + * @return the long integer value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a long integer + */ + public static long longValue(Object value) throws NumberFormatException { + if (value == null) + return 0L; + Class c = value.getClass(); + if (c.getSuperclass() == Number.class) + return ((Number) value).longValue(); + if (c == Boolean.class) + return ((Boolean) value).booleanValue() ? 1 : 0; + if (c == Character.class) + return ((Character) value).charValue(); + return Long.parseLong(stringValue(value, true)); + } + + /** + * Evaluates the given object as a double-precision floating-point number. + * + * @param value + * an object to interpret as a double + * @return the double value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a double + */ + public static double doubleValue(Object value) throws NumberFormatException { + if (value == null) + return 0.0; + Class c = value.getClass(); + if (c.getSuperclass() == Number.class) + return ((Number) value).doubleValue(); + if (c == Boolean.class) + return ((Boolean) value).booleanValue() ? 1 : 0; + if (c == Character.class) + return ((Character) value).charValue(); + String s = stringValue(value, true); + + return (s.length() == 0) ? 0.0 : Double.parseDouble(s); + /* + * For 1.1 parseDouble() is not available + */ + // return Double.valueOf( value.toString() ).doubleValue(); + } + + /** + * Evaluates the given object as a BigInteger. + * + * @param value + * an object to interpret as a BigInteger + * @return the BigInteger value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a BigInteger + */ + public static BigInteger bigIntValue(Object value) + throws NumberFormatException { + if (value == null) + return BigInteger.valueOf(0L); + Class c = value.getClass(); + if (c == BigInteger.class) + return (BigInteger) value; + if (c == BigDecimal.class) + return ((BigDecimal) value).toBigInteger(); + if (c.getSuperclass() == Number.class) + return BigInteger.valueOf(((Number) value).longValue()); + if (c == Boolean.class) + return BigInteger.valueOf(((Boolean) value).booleanValue() ? 1 : 0); + if (c == Character.class) + return BigInteger.valueOf(((Character) value).charValue()); + return new BigInteger(stringValue(value, true)); + } + + /** + * Evaluates the given object as a BigDecimal. + * + * @param value + * an object to interpret as a BigDecimal + * @return the BigDecimal value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a BigDecimal + */ + public static BigDecimal bigDecValue(Object value) + throws NumberFormatException { + if (value == null) + return BigDecimal.valueOf(0L); + Class c = value.getClass(); + if (c == BigDecimal.class) + return (BigDecimal) value; + if (c == BigInteger.class) + return new BigDecimal((BigInteger) value); + if (c.getSuperclass() == Number.class) + return new BigDecimal(((Number) value).doubleValue()); + if (c == Boolean.class) + return BigDecimal.valueOf(((Boolean) value).booleanValue() ? 1 : 0); + if (c == Character.class) + return BigDecimal.valueOf(((Character) value).charValue()); + return new BigDecimal(stringValue(value, true)); + } + + /** + * Evaluates the given object as a String and trims it if the trim flag is + * true. + * + * @param value + * an object to interpret as a String + * @return the String value implied by the given object as returned by the + * toString() method, or "null" if the object is null. + */ + public static String stringValue(Object value, boolean trim) { + String result; + + if (value == null) { + result = NULL_STRING; + } else { + result = value.toString(); + if (trim) { + result = result.trim(); + } + } + return result; + } + + /** + * Evaluates the given object as a String. + * + * @param value + * an object to interpret as a String + * @return the String value implied by the given object as returned by the + * toString() method, or "null" if the object is null. + */ + public static String stringValue(Object value) { + return stringValue(value, false); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java new file mode 100644 index 000000000..949eede71 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java @@ -0,0 +1,124 @@ +/* + * 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.conversion.impl; + +import java.util.Map; + + +/** + * EnumTypeConverter + * + * + * This class converts java 5 enums to String and from String[] to enum. + *

+ * One of Java 5's improvements is providing enumeration facility. + * Up to now, there existed no enumerations. The only way to simulate was the so-called int Enum pattern: + * {code} + * public static final int SEASON_WINTER = 0; + * public static final int SEASON_SPRING = 1; + * public static final int SEASON_SUMMER = 2; + * public static final int SEASON_FALL = 3; + * {code} + *

+ * Java 5.0 now provides the following construct: + * {code} + * public static enum Season { WINTER, SPRING, SUMMER, FALL }; + * {code} + * + * + * + * h3. Implementing Java 5 Enumeration Type Conversion + *

+ * 1. myAction-conversion.properties* + *

+ * Place a myAction-conversion.properties-file in the path of your Action. + * Add the following entry to the properties-file: + * {code} + * nameOfYourField=fullyClassifiedNameOfYourConverter + * {code} + *   + *

+ * 2. myAction.java* + * Your action contains the _enumeration_: + * {code} + * public enum Criticality {DEBUG, INFO, WARNING, ERROR, FATAL} + * {code} + *   + * * Your action contains the _private field_: + * {code} + * private myEnum myFieldForEnum; + * {code} + *   + * Your action contains _getters and setters_ for your field: + * {code} + * public myEnum getCriticality() { + * return myFieldForEnum; + * } + * + * public void setCriticality(myEnum myFieldForEnum) { + * this.myFieldForEnum= myFieldForEnum; + * } + * {code} + *

+ * 3. JSP* + *

+ *     In your jsp you can access an enumeration value just normal by using the known -Tag: + * {code} + * + * {code} + * + * + * @author Tamara Cattivelli + * @author Rainer Hermanns + * @version $Id$ + * @deprecated Since Struts 2.1.0 as enum support is now built into XWork + */ +@Deprecated public class EnumTypeConverter extends DefaultTypeConverter { + + /** + * Converts the given object to a given type. How this is to be done is implemented in toClass. The OGNL context, o + * and toClass are given. This method should be able to handle conversion in general without any context or object + * specified. + * + * @param context - OGNL context under which the conversion is being done + * @param o - the object to be converted + * @param toClass - the class that contains the code to convert to enumeration + * @return Converted value of type declared in toClass or TypeConverter.NoConversionPossible to indicate that the + * conversion was not possible. + */ + @Override + public Object convertValue(Map context, Object o, Class toClass) { + if (o instanceof String[]) { + return convertFromString(((String[]) o)[0], toClass); + } else if (o instanceof String) { + return convertFromString((String) o, toClass); + } + + return super.convertValue(context, o, toClass); + } + + /** + * Converts one or more String values to the specified class. + * @param value - the String values to be converted, such as those submitted from an HTML form + * @param toClass - the class to convert to + * @return the converted object + */ + public java.lang.Enum convertFromString(String value, Class toClass) { + return Enum.valueOf(toClass, value); + } + +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java new file mode 100644 index 000000000..69f1ced78 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java @@ -0,0 +1,38 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + + +/** + * GenericsObjectTypeDeterminer + * + * @author Patrick Lightbody + * @author Rainer Hermanns + * @author Alexandru Popescu + * + * @deprecated Use DefaultObjectTypeDeterminer instead. Since XWork 2.0.4 the DefaultObjectTypeDeterminer handles the + * annotation processing. + */ +@Deprecated public class GenericsObjectTypeDeterminer extends DefaultObjectTypeDeterminer { + + public GenericsObjectTypeDeterminer(XWorkConverter conv, + XWorkBasicConverter basicConv, ReflectionProvider prov) { + super(conv, prov); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.java new file mode 100644 index 000000000..11f723f77 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.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.conversion.impl; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.NullHandler; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + +import java.beans.PropertyDescriptor; +import java.util.*; + + +/** + * + * + * Provided that the key {@link ReflectionContextState#CREATE_NULL_OBJECTS} is in the action context with a value of true (this key is set + * only during the execution of the {@link com.opensymphony.xwork2.interceptor.ParametersInterceptor}), OGNL expressions + * that have caused a NullPointerException will be temporarily stopped for evaluation while the system automatically + * tries to solve the null references by automatically creating the object. + * + *

The following rules are used when handling null references: + * + *

    + * + *
  • If the property is declared exactly as a {@link Collection} or {@link List}, then an ArrayList shall be + * returned and assigned to the null references.
  • + * + *
  • If the property is declared as a {@link Map}, then a HashMap will be returned and assigned to the null + * references.
  • + * + *
  • If the null property is a simple bean with a no-arg constructor, it will simply be created using the {@link + * ObjectFactory#buildBean(java.lang.Class, java.util.Map)} method.
  • + * + *
+ * + * + * + * + * + * For example, if a form element has a text field named person.name and the expression person evaluates + * to null, then this class will be invoked. Because the person expression evaluates to a Person class, a + * new Person is created and assigned to the null reference. Finally, the name is set on that object and the overall + * effect is that the system automatically created a Person object for you, set it by calling setUsers() and then + * finally called getUsers().setName() as you would typically expect. + * + * + *

+ * XWork will automatically handle the most common type conversion for you. This includes support for converting to + * and from Strings for each of the following: + *

+ *

    + *
  • String
  • + *
  • boolean / Boolean
  • + *
  • char / Character
  • + *
  • int / Integer, float / Float, long / Long, double / Double
  • + *
  • dates - uses the SHORT format for the Locale associated with the current request
  • + *
  • arrays - assuming the individual strings can be coverted to the individual items
  • + *
  • collections - if not object type can be determined, it is assumed to be a String and a new ArrayList is + * created
  • + *
+ *

Note that with arrays the type conversion will defer to the type of the array elements and try to convert each + * item individually. As with any other type conversion, if the conversion can't be performed the standard type + * conversion error reporting is used to indicate a problem occured while processing the type conversion. + *

+ * + * + * @author Pat Lightbody + * @author Mike Mosiewicz + * @author Rainer Hermanns + * @author Alexandru Popescu + */ +public class XWorkBasicConverter extends DefaultTypeConverter { + + private static String MILLISECOND_FORMAT = ".SSS"; + + private ObjectTypeDeterminer objectTypeDeterminer; + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer det) { + this.objectTypeDeterminer = det; + } + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Override + public Object convertValue(Map context, Object o, Member member, String s, Object value, Class toType) { + Object result = null; + + if (value == null || toType.isAssignableFrom(value.getClass())) { + // no need to convert at all, right? + return value; + } + + if (toType == String.class) { + /* the code below has been disabled as it causes sideffects in Struts2 (XW-512) + // if input (value) is a number then use special conversion method (XW-490) + Class inputType = value.getClass(); + if (Number.class.isAssignableFrom(inputType)) { + result = doConvertFromNumberToString(context, value, inputType); + if (result != null) { + return result; + } + }*/ + // okay use default string conversion + result = doConvertToString(context, value); + } else if (toType == boolean.class) { + result = doConvertToBoolean(value); + } else if (toType == Boolean.class) { + result = doConvertToBoolean(value); + } else if (toType.isArray()) { + result = doConvertToArray(context, o, member, s, value, toType); + } else if (Date.class.isAssignableFrom(toType)) { + result = doConvertToDate(context, value, toType); + } else if (Calendar.class.isAssignableFrom(toType)) { + Date dateResult = (Date) doConvertToDate(context, value, Date.class); + if (dateResult != null) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(dateResult); + result = calendar; + } + } else if (Collection.class.isAssignableFrom(toType)) { + result = doConvertToCollection(context, o, member, s, value, toType); + } else if (toType == Character.class) { + result = doConvertToCharacter(value); + } else if (toType == char.class) { + result = doConvertToCharacter(value); + } else if (Number.class.isAssignableFrom(toType) || toType.isPrimitive()) { + result = doConvertToNumber(context, value, toType); + } else if (toType == Class.class) { + result = doConvertToClass(value); + } + + if (result == null) { + if (value instanceof Object[]) { + Object[] array = (Object[]) value; + + if (array.length >= 1) { + value = array[0]; + } else { + value = null; + } + + // let's try to convert the first element only + result = convertValue(context, o, member, s, value, toType); + } else if (!"".equals(value)) { // we've already tried the types we know + result = super.convertValue(context, value, toType); + } + + if (result == null && value != null && !"".equals(value)) { + throw new XWorkException("Cannot create type " + toType + " from value " + value); + } + } + + return result; + } + + private Locale getLocale(Map context) { + if (context == null) { + return Locale.getDefault(); + } + + Locale locale = (Locale) context.get(ActionContext.LOCALE); + + if (locale == null) { + locale = Locale.getDefault(); + } + + return locale; + } + + /** + * Creates a Collection of the specified type. + * + * @param fromObject + * @param propertyName + * @param toType the type of Collection to create + * @param memberType the type of object elements in this collection must be + * @param size the initial size of the collection (ignored if 0 or less) + * @return a Collection of the specified type + */ + private Collection createCollection(Object fromObject, String propertyName, Class toType, Class memberType, int size) { +// try { +// Object original = Ognl.getValue(OgnlUtil.compile(propertyName),fromObject); +// if (original instanceof Collection) { +// Collection coll = (Collection) original; +// coll.clear(); +// return coll; +// } +// } catch (Exception e) { +// // fail back to creating a new one +// } + + Collection result; + + if (toType == Set.class) { + if (size > 0) { + result = new HashSet(size); + } else { + result = new HashSet(); + } + } else if (toType == SortedSet.class) { + result = new TreeSet(); + } else { + if (size > 0) { + result = new XWorkList(objectFactory, xworkConverter, memberType, size); + } else { + result = new XWorkList(objectFactory, xworkConverter, memberType); + } + } + + return result; + } + + private Object doConvertToArray(Map context, Object o, Member member, String s, Object value, Class toType) { + Object result = null; + Class componentType = toType.getComponentType(); + + if (componentType != null) { + TypeConverter converter = getTypeConverter(context); + + if (value.getClass().isArray()) { + int length = Array.getLength(value); + result = Array.newInstance(componentType, length); + + for (int i = 0; i < length; i++) { + Object valueItem = Array.get(value, i); + Array.set(result, i, converter.convertValue(context, o, member, s, valueItem, componentType)); + } + } else { + result = Array.newInstance(componentType, 1); + Array.set(result, 0, converter.convertValue(context, o, member, s, value, componentType)); + } + } + + return result; + } + + private Object doConvertToCharacter(Object value) { + if (value instanceof String) { + String cStr = (String) value; + + return (cStr.length() > 0) ? new Character(cStr.charAt(0)) : null; + } + + return null; + } + + private Object doConvertToBoolean(Object value) { + if (value instanceof String) { + String bStr = (String) value; + + return Boolean.valueOf(bStr); + } + + return null; + } + + private Class doConvertToClass(Object value) { + Class clazz = null; + + if (value instanceof String && value != null && ((String) value).length() > 0) { + try { + clazz = Class.forName((String) value); + } catch (ClassNotFoundException e) { + throw new XWorkException(e.getLocalizedMessage(), e); + } + } + + return clazz; + } + + private Collection doConvertToCollection(Map context, Object o, Member member, String prop, Object value, Class toType) { + Collection result; + Class memberType = String.class; + + if (o != null) { + //memberType = (Class) XWorkConverter.getInstance().getConverter(o.getClass(), XWorkConverter.CONVERSION_COLLECTION_PREFIX + prop); + memberType = objectTypeDeterminer.getElementClass(o.getClass(), prop, null); + + if (memberType == null) { + memberType = String.class; + } + } + + if (toType.isAssignableFrom(value.getClass())) { + // no need to do anything + result = (Collection) value; + } else if (value.getClass().isArray()) { + Object[] objArray = (Object[]) value; + TypeConverter converter = getTypeConverter(context); + result = createCollection(o, prop, toType, memberType, objArray.length); + + for (Object anObjArray : objArray) { + result.add(converter.convertValue(context, o, member, prop, anObjArray, memberType)); + } + } else if (Collection.class.isAssignableFrom(value.getClass())) { + Collection col = (Collection) value; + TypeConverter converter = getTypeConverter(context); + result = createCollection(o, prop, toType, memberType, col.size()); + + for (Object aCol : col) { + result.add(converter.convertValue(context, o, member, prop, aCol, memberType)); + } + } else { + result = createCollection(o, prop, toType, memberType, -1); + result.add(value); + } + + return result; + } + + private Object doConvertToDate(Map context, Object value, Class toType) { + Date result = null; + + if (value instanceof String && value != null && ((String) value).length() > 0) { + String sa = (String) value; + Locale locale = getLocale(context); + + DateFormat df = null; + if (java.sql.Time.class == toType) { + df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); + } else if (java.sql.Timestamp.class == toType) { + Date check = null; + SimpleDateFormat dtfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.MEDIUM, + locale); + SimpleDateFormat fullfmt = new SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT, + locale); + + SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, + locale); + + SimpleDateFormat[] fmts = {fullfmt, dtfmt, dfmt}; + for (SimpleDateFormat fmt : fmts) { + try { + check = fmt.parse(sa); + df = fmt; + if (check != null) { + break; + } + } catch (ParseException ignore) { + } + } + } else if (java.util.Date.class == toType) { + Date check = null; + DateFormat[] dfs = getDateFormats(locale); + for (DateFormat df1 : dfs) { + try { + check = df1.parse(sa); + df = df1; + if (check != null) { + break; + } + } + catch (ParseException ignore) { + } + } + } + //final fallback for dates without time + if (df == null) { + df = DateFormat.getDateInstance(DateFormat.SHORT, locale); + } + try { + df.setLenient(false); // let's use strict parsing (XW-341) + result = df.parse(sa); + if (!(Date.class == toType)) { + try { + Constructor constructor = toType.getConstructor(new Class[]{long.class}); + return constructor.newInstance(new Object[]{Long.valueOf(result.getTime())}); + } catch (Exception e) { + throw new XWorkException("Couldn't create class " + toType + " using default (long) constructor", e); + } + } + } catch (ParseException e) { + throw new XWorkException("Could not parse date", e); + } + } else if (Date.class.isAssignableFrom(value.getClass())) { + result = (Date) value; + } + return result; + } + + private DateFormat[] getDateFormats(Locale locale) { + DateFormat dt1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale); + DateFormat dt2 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); + DateFormat dt3 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); + + DateFormat d1 = DateFormat.getDateInstance(DateFormat.SHORT, locale); + DateFormat d2 = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + DateFormat d3 = DateFormat.getDateInstance(DateFormat.LONG, locale); + + DateFormat rfc3399 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + DateFormat[] dfs = {dt1, dt2, dt3, rfc3399, d1, d2, d3}; //added RFC 3339 date format (XW-473) + return dfs; + } + + private Object doConvertToNumber(Map context, Object value, Class toType) { + if (value instanceof String) { + if (toType == BigDecimal.class) { + return new BigDecimal((String) value); + } else if (toType == BigInteger.class) { + return new BigInteger((String) value); + } else if (toType.isPrimitive()) { + Object convertedValue = super.convertValue(context, value, toType); + String stringValue = (String) value; + if (!isInRange((Number)convertedValue, stringValue, toType)) + throw new XWorkException("Overflow or underflow casting: \"" + stringValue + "\" into class " + convertedValue.getClass().getName()); + + return convertedValue; + } else { + String stringValue = (String) value; + if (!toType.isPrimitive() && (stringValue == null || stringValue.length() == 0)) { + return null; + } + NumberFormat numFormat = NumberFormat.getInstance(getLocale(context)); + ParsePosition parsePos = new ParsePosition(0); + if (isIntegerType(toType)) { + numFormat.setParseIntegerOnly(true); + } + numFormat.setGroupingUsed(true); + Number number = numFormat.parse(stringValue, parsePos); + + if (parsePos.getIndex() != stringValue.length()) { + throw new XWorkException("Unparseable number: \"" + stringValue + "\" at position " + + parsePos.getIndex()); + } else { + if (!isInRange(number, stringValue, toType)) + throw new XWorkException("Overflow or underflow casting: \"" + stringValue + "\" into class " + number.getClass().getName()); + + value = super.convertValue(context, number, toType); + } + } + } else if (value instanceof Object[]) { + Object[] objArray = (Object[]) value; + + if (objArray.length == 1) { + return doConvertToNumber(context, objArray[0], toType); + } + } + + // pass it through DefaultTypeConverter + return super.convertValue(context, value, toType); + } + + protected boolean isInRange(Number value, String stringValue, Class toType) { + Number bigValue = null; + Number lowerBound = null; + Number upperBound = null; + + try { + if (double.class == toType || Double.class == toType) { + bigValue = new BigDecimal(stringValue); + // Double.MIN_VALUE is the smallest positive non-zero number + lowerBound = BigDecimal.valueOf(Double.MAX_VALUE).negate(); + upperBound = BigDecimal.valueOf(Double.MAX_VALUE); + } else if (float.class == toType || Float.class == toType) { + bigValue = new BigDecimal(stringValue); + // Float.MIN_VALUE is the smallest positive non-zero number + lowerBound = BigDecimal.valueOf(Float.MAX_VALUE).negate(); + upperBound = BigDecimal.valueOf(Float.MAX_VALUE); + } else if (byte.class == toType || Byte.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Byte.MIN_VALUE); + upperBound = BigInteger.valueOf(Byte.MAX_VALUE); + } else if (char.class == toType || Character.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Character.MIN_VALUE); + upperBound = BigInteger.valueOf(Character.MAX_VALUE); + } else if (short.class == toType || Short.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Short.MIN_VALUE); + upperBound = BigInteger.valueOf(Short.MAX_VALUE); + } else if (int.class == toType || Integer.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Integer.MIN_VALUE); + upperBound = BigInteger.valueOf(Integer.MAX_VALUE); + } else if (long.class == toType || Long.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Long.MIN_VALUE); + upperBound = BigInteger.valueOf(Long.MAX_VALUE); + } + } catch (NumberFormatException e) { + //shoult it fail here? BigInteger doesnt seem to be so nice parsing numbers as NumberFormat + return true; + } + + return ((Comparable)bigValue).compareTo(lowerBound) >= 0 && ((Comparable)bigValue).compareTo(upperBound) <= 0; + } + + protected boolean isIntegerType(Class type) { + if (double.class == type || float.class == type || Double.class == type || Float.class == type + || char.class == type || Character.class == type) { + return false; + } + + return true; + } + + /** + * Converts the input as a number using java's number formatter to a string output. + */ + private String doConvertFromNumberToString(Map context, Object value, Class toType) { + // XW-409: If the input is a Number we should format it to a string using the choosen locale and use java's numberformatter + if (Number.class.isAssignableFrom(toType)) { + NumberFormat numFormat = NumberFormat.getInstance(getLocale(context)); + if (isIntegerType(toType)) { + numFormat.setParseIntegerOnly(true); + } + numFormat.setGroupingUsed(true); + numFormat.setMaximumFractionDigits(99); // to be sure we include all digits after decimal seperator, otherwise some of the fractions can be chopped + + String number = numFormat.format(value); + if (number != null) { + return number; + } + } + + return null; // no number + } + + + private String doConvertToString(Map context, Object value) { + String result = null; + + if (value instanceof int[]) { + int[] x = (int[]) value; + List intArray = new ArrayList(x.length); + + for (int aX : x) { + intArray.add(Integer.valueOf(aX)); + } + + result = StringUtils.join(intArray, ", "); + } else if (value instanceof long[]) { + long[] x = (long[]) value; + List longArray = new ArrayList(x.length); + + for (long aX : x) { + longArray.add(Long.valueOf(aX)); + } + + result = StringUtils.join(longArray, ", "); + } else if (value instanceof double[]) { + double[] x = (double[]) value; + List doubleArray = new ArrayList(x.length); + + for (double aX : x) { + doubleArray.add(new Double(aX)); + } + + result = StringUtils.join(doubleArray, ", "); + } else if (value instanceof boolean[]) { + boolean[] x = (boolean[]) value; + List booleanArray = new ArrayList(x.length); + + for (boolean aX : x) { + booleanArray.add(new Boolean(aX)); + } + + result = StringUtils.join(booleanArray, ", "); + } else if (value instanceof Date) { + DateFormat df = null; + if (value instanceof java.sql.Time) { + df = DateFormat.getTimeInstance(DateFormat.MEDIUM, getLocale(context)); + } else if (value instanceof java.sql.Timestamp) { + SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.MEDIUM, + getLocale(context)); + df = new SimpleDateFormat(dfmt.toPattern() + MILLISECOND_FORMAT); + } else { + df = DateFormat.getDateInstance(DateFormat.SHORT, getLocale(context)); + } + result = df.format(value); + } else if (value instanceof String[]) { + result = StringUtils.join((String[]) value, ", "); + } + + return result; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java new file mode 100644 index 000000000..aae1c9df8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java @@ -0,0 +1,839 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkMessages; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.annotations.Conversion; +import com.opensymphony.xwork2.conversion.annotations.ConversionRule; +import com.opensymphony.xwork2.conversion.annotations.ConversionType; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper; +import com.opensymphony.xwork2.util.*; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.text.MessageFormat; + + +/** + * XWorkConverter is a singleton used by many of the Struts 2's Ognl extention points, + * such as InstantiatingNullHandler, XWorkListPropertyAccessor etc to do object + * conversion. + *

+ * + *

+ * Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web + * is type-agnostic (everything is a string in HTTP), Struts 2's type conversion features are very useful. For instance, + * if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have + * Struts 2 do the conversion both from String to Point and from Point to String. + *

+ *

Using this "point" example, if your action (or another compound object in which you are setting properties on) + * has a corresponding ClassName-conversion.properties file, Struts 2 will use the configured type converters for + * conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following + * entry to ClassName-conversion.properties (Note that the PointConverter should impl the TypeConverter + * interface): + *

+ *

point = com.acme.PointConverter + *

+ *

Your type converter should be sure to check what class type it is being requested to convert. Because it is used + * for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in + * to Points, and one that turns Points in to Strings. + *

+ *

After this is done, you can now reference your point (using <s:property value="point"/> in JSP or ${point} + * in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be + * converted back to a Point once again. + *

+ *

In some situations you may wish to apply a type converter globally. This can be done by editing the file + * xwork-conversion.properties in the root of your class path (typically WEB-INF/classes) and providing a + * property in the form of the class name of the object you wish to convert on the left hand side and the class name of + * the type converter on the right hand side. For example, providing a type converter for all Point objects would mean + * adding the following entry: + *

+ *

com.acme.Point = com.acme.PointConverter + *

+ * + *

+ *

+ *

+ * + *

+ * Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out + * properly formatted dates. Rather, you should use the i18n features of Struts 2 (and consult the JavaDocs for JDK's + * MessageFormat object) to see how a properly formatted date should be displayed. + *

+ * + *

+ *

+ *

+ * + *

+ * Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the + * input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string, + * "", cannot be converted to a number might not be important - especially in a web environment where it is hard to + * distinguish between a user not entering a value vs. entering a blank value. + *

+ *

By default, all conversion errors are reported using the generic i18n key xwork.default.invalid.fieldvalue, + * which you can override (the default text is Invalid field value for field "xxx", where xxx is the field name) + * in your global i18n resource bundle. + *

+ *

However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n + * key associated with just your action (Action.properties) using the pattern invalid.fieldvalue.xxx, where xxx + * is the field name. + *

+ *

It is important to know that none of these errors are actually reported directly. Rather, they are added to a map + * called conversionErrors in the ActionContext. There are several ways this map can then be accessed and the + * errors can be reported accordingly. + *

+ * + * + * @author Pat Lightbody + * @author Rainer Hermanns + * @author Alexandru Popescu + * @author tm_jee + * @version $Date$ $Id$ + * @see XWorkBasicConverter + */ +public class XWorkConverter extends DefaultTypeConverter { + + protected static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class); + public static final String REPORT_CONVERSION_ERRORS = "report.conversion.errors"; + public static final String CONVERSION_PROPERTY_FULLNAME = "conversion.property.fullName"; + public static final String CONVERSION_ERROR_PROPERTY_PREFIX = "invalid.fieldvalue."; + public static final String CONVERSION_COLLECTION_PREFIX = "Collection_"; + + public static final String LAST_BEAN_CLASS_ACCESSED = "last.bean.accessed"; + public static final String LAST_BEAN_PROPERTY_ACCESSED = "last.property.accessed"; + public static final String MESSAGE_INDEX_PATTERN = "\\[\\d+\\]\\."; + public static final String MESSAGE_INDEX_BRACKET_PATTERN = "[\\[\\]\\.]"; + public static final String PERIOD = "."; + public static final Pattern messageIndexPattern = Pattern.compile(MESSAGE_INDEX_PATTERN); + + /** + * Target class conversion Mappings. + *

+     * Map>
+     *  - Class -> convert to class
+     *  - Map
+     *    - String -> property name
+     *                eg. Element_property, property etc.
+     *    - Object -> String to represent properties
+     *                eg. value part of
+     *                    KeyProperty_property=id
+     *             -> TypeConverter to represent an Ognl TypeConverter
+     *                eg. value part of
+     *                    property=foo.bar.MyConverter
+     *             -> Class to represent a class
+     *                eg. value part of
+     *                    Element_property=foo.bar.MyObject
+     * 
+ */ + protected HashMap> mappings = new HashMap>(); // action + + /** + * Unavailable target class conversion mappings, serves as a simple cache. + */ + protected HashSet noMapping = new HashSet(); // action + + /** + * Record class and its type converter mapping. + *
+     * - String - classname as String
+     * - TypeConverter - instance of TypeConverter
+     * 
+ */ + protected HashMap defaultMappings = new HashMap(); // non-action (eg. returned value) + + /** + * Record classes that doesn't have conversion mapping defined. + *
+     * - String -> classname as String
+     * 
+ */ + protected HashSet unknownMappings = new HashSet(); // non-action (eg. returned value) + + private TypeConverter defaultTypeConverter; + private ObjectFactory objectFactory; + + + protected XWorkConverter() { + } + + @Inject + public void setObjectFactory(ObjectFactory factory) { + this.objectFactory = factory; + // note: this file is deprecated + loadConversionProperties("xwork-default-conversion.properties"); + + loadConversionProperties("xwork-conversion.properties"); + } + + @Inject + public void setDefaultTypeConverter(XWorkBasicConverter conv) { + this.defaultTypeConverter = conv; + } + + public static String getConversionErrorMessage(String propertyName, ValueStack stack) { + String defaultMessage = LocalizedTextUtil.findDefaultText(XWorkMessages.DEFAULT_INVALID_FIELDVALUE, + ActionContext.getContext().getLocale(), + new Object[]{ + propertyName + }); + + List indexValues = getIndexValues(propertyName); + + propertyName = removeAllIndexesInProperyName(propertyName); + + String getTextExpression = "getText('" + CONVERSION_ERROR_PROPERTY_PREFIX + propertyName + "','" + defaultMessage + "')"; + String message = (String) stack.findValue(getTextExpression); + + if (message == null) { + message = defaultMessage; + } else { + message = MessageFormat.format(message, indexValues.toArray()); + } + + return message; + } + + private static String removeAllIndexesInProperyName(String propertyName) { + return propertyName.replaceAll(MESSAGE_INDEX_PATTERN, PERIOD); + } + + private static List getIndexValues(String propertyName) { + Matcher matcher = messageIndexPattern.matcher(propertyName); + List indexes = new ArrayList(); + while (matcher.find()) { + Integer index = new Integer(matcher.group().replaceAll(MESSAGE_INDEX_BRACKET_PATTERN, "")) + 1; + indexes.add(Integer.toString(index)); + } + return indexes; + } + + public static String buildConverterFilename(Class clazz) { + String className = clazz.getName(); + return className.replace('.', '/') + "-conversion.properties"; + } + + @Override + public Object convertValue(Map map, Object o, Class aClass) { + return convertValue(map, null, null, null, o, aClass); + } + + /** + * Convert value from one form to another. + * Minimum requirement of arguments: + *
    + *
  • supplying context, toClass and value
  • + *
  • supplying context, target and value.
  • + *
+ * + * @see TypeConverter#convertValue(java.util.Map, java.lang.Object, java.lang.reflect.Member, java.lang.String, java.lang.Object, java.lang.Class) + */ + @Override + public Object convertValue(Map context, Object target, Member member, String property, Object value, Class toClass) { + // + // Process the conversion using the default mappings, if one exists + // + TypeConverter tc = null; + + if ((value != null) && (toClass == value.getClass())) { + return value; + } + + // allow this method to be called without any context + // i.e. it can be called with as little as "Object value" and "Class toClass" + if (target != null) { + Class clazz = target.getClass(); + + Object[] classProp = null; + + // this is to handle weird issues with setValue with a different type + if ((target instanceof CompoundRoot) && (context != null)) { + classProp = getClassProperty(context); + } + + if (classProp != null) { + clazz = (Class) classProp[0]; + property = (String) classProp[1]; + } + + tc = (TypeConverter) getConverter(clazz, property); + + if (LOG.isDebugEnabled()) + LOG.debug("field-level type converter for property [" + property + "] = " + (tc == null ? "none found" : tc)); + } + + if (tc == null && context != null) { + // ok, let's see if we can look it up by path as requested in XW-297 + Object lastPropertyPath = context.get(ReflectionContextState.CURRENT_PROPERTY_PATH); + Class clazz = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + if (lastPropertyPath != null && clazz != null) { + String path = lastPropertyPath + "." + property; + tc = (TypeConverter) getConverter(clazz, path); + } + } + + if (tc == null) { + if (toClass.equals(String.class) && (value != null) && !(value.getClass().equals(String.class) || value.getClass().equals(String[].class))) { + // when converting to a string, use the source target's class's converter + tc = lookup(value.getClass()); + } else { + // when converting from a string, use the toClass's converter + tc = lookup(toClass); + } + + if (LOG.isDebugEnabled()) + LOG.debug("global-level type converter for property [" + property + "] = " + (tc == null ? "none found" : tc)); + } + + + if (tc != null) { + try { + return tc.convertValue(context, target, member, property, value, toClass); + } catch (Exception e) { + if (LOG.isDebugEnabled()) + LOG.debug("unable to convert value using type converter [#0]", e, tc.getClass().getName()); + handleConversionException(context, property, value, target); + + return TypeConverter.NO_CONVERSION_POSSIBLE; + } + } + + if (defaultTypeConverter != null) { + try { + if (LOG.isDebugEnabled()) + LOG.debug("falling back to default type converter [" + defaultTypeConverter + "]"); + return defaultTypeConverter.convertValue(context, target, member, property, value, toClass); + } catch (Exception e) { + if (LOG.isDebugEnabled()) + LOG.debug("unable to convert value using type converter [#0]", e, defaultTypeConverter.getClass().getName()); + handleConversionException(context, property, value, target); + + return TypeConverter.NO_CONVERSION_POSSIBLE; + } + } else { + try { + if (LOG.isDebugEnabled()) + LOG.debug("falling back to Ognl's default type conversion"); + return super.convertValue(value, toClass); + } catch (Exception e) { + if (LOG.isDebugEnabled()) + LOG.debug("unable to convert value using type converter [#0]", e, super.getClass().getName()); + handleConversionException(context, property, value, target); + + return TypeConverter.NO_CONVERSION_POSSIBLE; + } + } + } + + /** + * Looks for a TypeConverter in the default mappings. + * + * @param className name of the class the TypeConverter must handle + * @return a TypeConverter to handle the specified class or null if none can be found + */ + public TypeConverter lookup(String className) { + if (unknownMappings.contains(className) && !defaultMappings.containsKey(className)) { + return null; + } + + TypeConverter result = defaultMappings.get(className); + + //Looks for super classes + if (result == null) { + Class clazz = null; + + try { + clazz = Thread.currentThread().getContextClassLoader().loadClass(className); + } catch (ClassNotFoundException cnfe) { + //swallow + } + + result = lookupSuper(clazz); + + if (result != null) { + //Register now, the next lookup will be faster + registerConverter(className, result); + } else { + // if it isn't found, never look again (also faster) + registerConverterNotFound(className); + } + } + + return result; + } + + /** + * Looks for a TypeConverter in the default mappings. + * + * @param clazz the class the TypeConverter must handle + * @return a TypeConverter to handle the specified class or null if none can be found + */ + public TypeConverter lookup(Class clazz) { + return lookup(clazz.getName()); + } + + protected Object getConverter(Class clazz, String property) { + if (LOG.isDebugEnabled()) { + LOG.debug("Property: " + property); + LOG.debug("Class: " + clazz.getName()); + } + synchronized (clazz) { + if ((property != null) && !noMapping.contains(clazz)) { + try { + Map mapping = mappings.get(clazz); + + if (mapping == null) { + mapping = buildConverterMapping(clazz); + } else { + mapping = conditionalReload(clazz, mapping); + } + + Object converter = mapping.get(property); + if (LOG.isDebugEnabled() && converter == null) { + LOG.debug("converter is null for property " + property + ". Mapping size: " + mapping.size()); + for (String next : mapping.keySet()) { + LOG.debug(next + ":" + mapping.get(next)); + } + } + return converter; + } catch (Throwable t) { + noMapping.add(clazz); + } + } + } + + return null; + } + + protected void handleConversionException(Map context, String property, Object value, Object object) { + if (context != null && (Boolean.TRUE.equals(context.get(REPORT_CONVERSION_ERRORS)))) { + String realProperty = property; + String fullName = (String) context.get(CONVERSION_PROPERTY_FULLNAME); + + if (fullName != null) { + realProperty = fullName; + } + + Map conversionErrors = (Map) context.get(ActionContext.CONVERSION_ERRORS); + + if (conversionErrors == null) { + conversionErrors = new HashMap(); + context.put(ActionContext.CONVERSION_ERRORS, conversionErrors); + } + + conversionErrors.put(realProperty, value); + } + } + + public synchronized void registerConverter(String className, TypeConverter converter) { + defaultMappings.put(className, converter); + if (unknownMappings.contains(className)) { + unknownMappings.remove(className); + } + } + + public synchronized void registerConverterNotFound(String className) { + unknownMappings.add(className); + } + + private Object[] getClassProperty(Map context) { + Object lastClass = context.get(LAST_BEAN_CLASS_ACCESSED); + Object lastProperty = context.get(LAST_BEAN_PROPERTY_ACCESSED); + return (lastClass != null && lastProperty != null) ? new Object[] {lastClass, lastProperty} : null; + } + + /** + * Looks for converter mappings for the specified class and adds it to an existing map. Only new converters are + * added. If a converter is defined on a key that already exists, the converter is ignored. + * + * @param mapping an existing map to add new converter mappings to + * @param clazz class to look for converter mappings for + */ + protected void addConverterMapping(Map mapping, Class clazz) { + try { + String converterFilename = buildConverterFilename(clazz); + InputStream is = FileManager.loadFile(converterFilename, clazz); + + if (is != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("processing conversion file [" + converterFilename + "] [class=" + clazz + "]"); + } + + Properties prop = new Properties(); + prop.load(is); + + for (Map.Entry entry : prop.entrySet()) { + String key = (String) entry.getKey(); + + if (mapping.containsKey(key)) { + break; + } + // for keyProperty of Set + if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) + || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) { + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as String]"); + } + mapping.put(key, entry.getValue()); + } + //for properties of classes + else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) || + key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) || + key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX)) + ) { + TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); + } + mapping.put(key, _typeConverter); + } + //for keys of Maps + else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) { + + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); + + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); + } + mapping.put(key, _typeConverter); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + converterClass + "]"); + } + mapping.put(key, converterClass); + } + } + //elements(values) of maps / lists + else { + Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + _c + "]"); + } + mapping.put(key, _c); + } + } + } + } catch (Exception ex) { + LOG.error("Problem loading properties for " + clazz.getName(), ex); + } + + // Process annotations + Annotation[] annotations = clazz.getAnnotations(); + + for (Annotation annotation : annotations) { + if (annotation instanceof Conversion) { + Conversion conversion = (Conversion) annotation; + + for (TypeConversion tc : conversion.conversions()) { + + String key = tc.key(); + + if (mapping.containsKey(key)) { + break; + } + if (LOG.isDebugEnabled()) { + LOG.debug(key + ":" + key); + } + + if (key != null) { + try { + if (tc.type() == ConversionType.APPLICATION) { + defaultMappings.put(key, createTypeConverter(tc.converter())); + } else { + if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY) || tc.rule().toString().equals(ConversionRule.CREATE_IF_NULL)) { + mapping.put(key, tc.value()); + } + //for properties of classes + else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) || + tc.rule().toString().equals(ConversionRule.KEY.toString()) || + tc.rule().toString().equals(ConversionRule.COLLECTION.toString()) + ) { + mapping.put(key, createTypeConverter(tc.converter())); + + + } + //for keys of Maps + else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) { + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); + if (LOG.isDebugEnabled()) { + LOG.debug("Converter class: " + converterClass); + } + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + mapping.put(key, createTypeConverter(tc.converter())); + } else { + mapping.put(key, converterClass); + if (LOG.isDebugEnabled()) { + LOG.debug("Object placed in mapping for key " + + key + + " is " + + mapping.get(key)); + } + + } + + } + //elements(values) of maps / lists + else { + mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); + } + } + } catch (Exception e) { + } + } + } + } + } + + Method[] methods = clazz.getMethods(); + + for (Method method : methods) { + + annotations = method.getAnnotations(); + + for (Annotation annotation : annotations) { + if (annotation instanceof TypeConversion) { + TypeConversion tc = (TypeConversion) annotation; + + String key = tc.key(); + if (mapping.containsKey(key)) { + break; + } + // Default to the property name + if (key != null && key.length() == 0) { + key = AnnotationUtils.resolvePropertyName(method); + LOG.debug("key from method name... " + key + " - " + method.getName()); + } + + + if (LOG.isDebugEnabled()) { + LOG.debug(key + ":" + key); + } + + if (key != null) { + try { + if (tc.type() == ConversionType.APPLICATION) { + defaultMappings.put(key, createTypeConverter(tc.converter())); + } else { + if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY)) { + mapping.put(key, tc.value()); + } + //for properties of classes + else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) || + tc.rule().toString().equals(ConversionRule.KEY.toString()) || + tc.rule().toString().equals(ConversionRule.COLLECTION.toString()) + ) { + mapping.put(key, createTypeConverter(tc.converter())); + } + //for keys of Maps + else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) { + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); + if (LOG.isDebugEnabled()) { + LOG.debug("Converter class: " + converterClass); + } + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + mapping.put(key, createTypeConverter(tc.converter())); + } else { + mapping.put(key, converterClass); + if (LOG.isDebugEnabled()) { + LOG.debug("Object placed in mapping for key " + + key + + " is " + + mapping.get(key)); + } + + } + + } + //elements(values) of maps / lists + else { + mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); + } + } + } catch (Exception e) { + } + } + } + } + } + } + + /** + * Looks for converter mappings for the specified class, traversing up its class hierarchy and interfaces and adding + * any additional mappings it may find. Mappings lower in the hierarchy have priority over those higher in the + * hierarcy. + * + * @param clazz the class to look for converter mappings for + * @return the converter mappings + */ + protected Map buildConverterMapping(Class clazz) throws Exception { + Map mapping = new HashMap(); + + // check for conversion mapping associated with super classes and any implemented interfaces + Class curClazz = clazz; + + while (!curClazz.equals(Object.class)) { + // add current class' mappings + addConverterMapping(mapping, curClazz); + + // check interfaces' mappings + Class[] interfaces = curClazz.getInterfaces(); + + for (Class anInterface : interfaces) { + addConverterMapping(mapping, anInterface); + } + + curClazz = curClazz.getSuperclass(); + } + + if (mapping.size() > 0) { + mappings.put(clazz, mapping); + } else { + noMapping.add(clazz); + } + + return mapping; + } + + private Map conditionalReload(Class clazz, Map oldValues) throws Exception { + Map mapping = oldValues; + + if (FileManager.isReloadingConfigs()) { + if (FileManager.fileNeedsReloading(buildConverterFilename(clazz), clazz)) { + mapping = buildConverterMapping(clazz); + } + } + + return mapping; + } + + TypeConverter createTypeConverter(String className) throws Exception { + // type converters are used across users + Object obj = objectFactory.buildBean(className, null); + if (obj instanceof TypeConverter) { + return (TypeConverter) obj; + + // For backwards compatibility + } else if (obj instanceof ognl.TypeConverter) { + return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj); + } else { + throw new IllegalArgumentException("Type converter class " + obj.getClass() + " doesn't implement com.opensymphony.xwork2.conversion.TypeConverter"); + } + } + + public void loadConversionProperties(String propsName) { + loadConversionProperties(propsName, false); + } + + public void loadConversionProperties(String propsName, boolean require) { + try { + Iterator resources = ClassLoaderUtil.getResources(propsName, getClass(), true); + while (resources.hasNext()) { + URL url = resources.next(); + Properties props = new Properties(); + props.load(url.openStream()); + + if (LOG.isDebugEnabled()) { + LOG.debug("processing conversion file [" + propsName + "]"); + } + + for (Object o : props.entrySet()) { + Map.Entry entry = (Map.Entry) o; + String key = (String) entry.getKey(); + + try { + TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + " [treated as TypeConverter " + _typeConverter + "]"); + } + defaultMappings.put(key, _typeConverter); + } catch (Exception e) { + LOG.error("Conversion registration error", e); + } + } + } + } catch (IOException ex) { + if (require) { + throw new XWorkException("Cannot load conversion properties file: "+propsName, ex); + } else { + LOG.debug("Cannot load conversion properties file: "+propsName, ex); + } + } + } + + /** + * Recurses through a class' interfaces and class hierarchy looking for a TypeConverter in the default mapping that + * can handle the specified class. + * + * @param clazz the class the TypeConverter must handle + * @return a TypeConverter to handle the specified class or null if none can be found + */ + TypeConverter lookupSuper(Class clazz) { + TypeConverter result = null; + + if (clazz != null) { + result = defaultMappings.get(clazz.getName()); + + if (result == null) { + // Looks for direct interfaces (depth = 1 ) + Class[] interfaces = clazz.getInterfaces(); + + for (Class anInterface : interfaces) { + if (defaultMappings.containsKey(anInterface.getName())) { + result = (TypeConverter) defaultMappings.get(anInterface.getName()); + break; + } + } + + if (result == null) { + // Looks for the superclass + // If 'clazz' is the Object class, an interface, a primitive type or void then clazz.getSuperClass() returns null + result = lookupSuper(clazz.getSuperclass()); + } + } + } + + return result; + } + + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java new file mode 100644 index 000000000..1234e2ef6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java @@ -0,0 +1,184 @@ +/* + * 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.conversion.metadata; + +import com.opensymphony.xwork2.conversion.annotations.ConversionRule; +import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * ConversionDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class ConversionDescription { + + /** + * Jakarta commons-logging reference. + */ + protected static Logger log = null; + + + public static final String KEY_PREFIX = "Key_"; + public static final String ELEMENT_PREFIX = "Element_"; + public static final String KEY_PROPERTY_PREFIX = "KeyProperty_"; + public static final String DEPRECATED_ELEMENT_PREFIX = "Collection_"; + + /** + * Key used for type conversion of maps. + */ + String MAP_PREFIX = "Map_"; + + public String property; + public String typeConverter = ""; + public String rule = ""; + public String value = ""; + public String fullQualifiedClassName; + public String type = null; + + public ConversionDescription() { + log = LoggerFactory.getLogger(this.getClass()); + } + + /** + * Creates an ConversionDescription with the specified property name. + * + * @param property + */ + public ConversionDescription(String property) { + this.property = property; + log = LoggerFactory.getLogger(this.getClass()); + } + + /** + *

+ * Sets the property name to be inserted into the related conversion.properties file.
+ * Note: Do not add COLLECTION_PREFIX or MAP_PREFIX keys to property names. + *

+ * + * @param property The property to be converted. + */ + public void setProperty(String property) { + this.property = property; + } + + /** + * Sets the class name of the type converter to be used. + * + * @param typeConverter The class name of the type converter. + */ + public void setTypeConverter(String typeConverter) { + this.typeConverter = typeConverter; + } + + /** + * Sets the rule prefix for COLLECTION_PREFIX or MAP_PREFIX key. + * Defaults to en emtpy String. + * + * @param rule + */ + public void setRule(String rule) { + if (rule != null && rule.length() > 0) { + if (rule.equals(ConversionRule.COLLECTION.toString())) { + this.rule = DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX; + } else if (rule.equals(ConversionRule.ELEMENT.toString())) { + this.rule = DefaultObjectTypeDeterminer.ELEMENT_PREFIX; + } else if (rule.equals(ConversionRule.KEY.toString())) { + this.rule = DefaultObjectTypeDeterminer.KEY_PREFIX; + } else if (rule.equals(ConversionRule.KEY_PROPERTY.toString())) { + this.rule = DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX; + } else if (rule.equals(ConversionRule.MAP.toString())) { + this.rule = MAP_PREFIX; + } + } + } + + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + /** + * Returns the conversion description as property entry. + *

+ * Example:
+ * property.name = converter.className
+ * Collection_property.name = converter.className
+ * Map_property.name = converter.className + * KeyProperty_name = id + *

+ * + * @return the conversion description as property entry. + */ + public String asProperty() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + try { + writer = new PrintWriter(sw); + writer.print(rule); + writer.print(property); + writer.print("="); + if ( rule.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) && value != null && value.length() > 0 ) { + writer.print(value); + } else { + writer.print(typeConverter); + } + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + + return sw.toString(); + + } + + /** + * Returns the fullQualifiedClassName attribute is used to create the special conversion.properties file name. + * + * @return fullQualifiedClassName + */ + public String getFullQualifiedClassName() { + return fullQualifiedClassName; + } + + /** + * The fullQualifiedClassName attribute is used to create the special conversion.properties file name. + * + * @param fullQualifiedClassName + */ + public void setFullQualifiedClassName(String fullQualifiedClassName) { + this.fullQualifiedClassName = fullQualifiedClassName; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html new file mode 100644 index 000000000..c50a611ab --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html @@ -0,0 +1 @@ +Type conversion meta data classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java new file mode 100644 index 000000000..44a9e2010 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +/** + * Context of a dependency construction. Used to manage circular references. + * + * @author crazybob@google.com (Bob Lee) + */ +class ConstructionContext { + + T currentReference; + boolean constructing; + + List> invocationHandlers; + + T getCurrentReference() { + return currentReference; + } + + void removeCurrentReference() { + this.currentReference = null; + } + + void setCurrentReference(T currentReference) { + this.currentReference = currentReference; + } + + boolean isConstructing() { + return constructing; + } + + void startConstruction() { + this.constructing = true; + } + + void finishConstruction() { + this.constructing = false; + invocationHandlers = null; + } + + Object createProxy(Class expectedType) { + // TODO: if I create a proxy which implements all the interfaces of + // the implementation type, I'll be able to get away with one proxy + // instance (as opposed to one per caller). + + if (!expectedType.isInterface()) { + throw new DependencyException( + expectedType.getName() + " is not an interface."); + } + + if (invocationHandlers == null) { + invocationHandlers = new ArrayList>(); + } + + DelegatingInvocationHandler invocationHandler = + new DelegatingInvocationHandler(); + invocationHandlers.add(invocationHandler); + + return Proxy.newProxyInstance( + expectedType.getClassLoader(), + new Class[] { expectedType }, + invocationHandler + ); + } + + void setProxyDelegates(T delegate) { + if (invocationHandlers != null) { + for (DelegatingInvocationHandler invocationHandler + : invocationHandlers) { + invocationHandler.setDelegate(delegate); + } + } + } + + static class DelegatingInvocationHandler implements InvocationHandler { + + T delegate; + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (delegate == null) { + throw new IllegalStateException( + "Not finished constructing. Please don't call methods on this" + + " object until the caller's construction is complete."); + } + + try { + return method.invoke(delegate, args); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + void setDelegate(T delegate) { + this.delegate = delegate; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Container.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Container.java new file mode 100644 index 000000000..c64bb7125 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Container.java @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.io.Serializable; +import java.util.Set; + +/** + * Injects dependencies into constructors, methods and fields annotated with + * {@link Inject}. Immutable. + * + *

When injecting a method or constructor, you can additionally annotate + * its parameters with {@link Inject} and specify a dependency name. When a + * parameter has no annotation, the container uses the name from the method or + * constructor's {@link Inject} annotation respectively. + * + *

For example: + * + *

+ *  class Foo {
+ *
+ *    // Inject the int constant named "i".
+ *    @Inject("i") int i;
+ *
+ *    // Inject the default implementation of Bar and the String constant
+ *    // named "s".
+ *    @Inject Foo(Bar bar, @Inject("s") String s) {
+ *      ...
+ *    }
+ *
+ *    // Inject the default implementation of Baz and the Bob implementation
+ *    // named "foo".
+ *    @Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
+ *      ...
+ *    }
+ *
+ *    // Inject the default implementation of Tee.
+ *    @Inject void setTee(Tee tee) {
+ *      ...
+ *    }
+ *  }
+ * 
+ * + *

To create and inject an instance of {@code Foo}: + * + *

+ *  Container c = ...;
+ *  Foo foo = c.inject(Foo.class);
+ * 
+ * + * @see ContainerBuilder + * @author crazybob@google.com (Bob Lee) + */ +public interface Container extends Serializable { + + /** + * Default dependency name. + */ + String DEFAULT_NAME = "default"; + + /** + * Injects dependencies into the fields and methods of an existing object. + */ + void inject(Object o); + + /** + * Creates and injects a new instance of type {@code implementation}. + */ + T inject(Class implementation); + + /** + * Gets an instance of the given dependency which was declared in + * {@link com.opensymphony.xwork2.inject.ContainerBuilder}. + */ + T getInstance(Class type, String name); + + /** + * Convenience method. Equivalent to {@code getInstance(type, + * DEFAULT_NAME)}. + */ + T getInstance(Class type); + + /** + * Gets a set of all registered names for the given type + * @param type The instance type + * @return A set of registered names + */ + Set getInstanceNames(Class type); + + /** + * Sets the scope strategy for the current thread. + */ + void setScopeStrategy(Scope.Strategy scopeStrategy); + + /** + * Removes the scope strategy for the current thread. + */ + void removeScopeStrategy(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java new file mode 100644 index 000000000..7eec8208d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java @@ -0,0 +1,527 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.lang.reflect.Member; +import java.util.*; +import java.util.logging.Logger; + +/** + * Builds a dependency injection {@link Container}. The combination of + * dependency type and name uniquely identifies a dependency mapping; you can + * use the same name for two different types. Not safe for concurrent use. + * + *

Adds the following factories by default: + * + *

    + *
  • Injects the current {@link Container}. + *
  • Injects the {@link Logger} for the injected member's declaring class. + *
+ * + * @author crazybob@google.com (Bob Lee) + */ +public final class ContainerBuilder { + + final Map, InternalFactory> factories = + new HashMap, InternalFactory>(); + final List> singletonFactories = + new ArrayList>(); + final List> staticInjections = new ArrayList>(); + boolean created; + boolean allowDuplicates = false; + + private static final InternalFactory CONTAINER_FACTORY = + new InternalFactory() { + public Container create(InternalContext context) { + return context.getContainer(); + } + }; + + private static final InternalFactory LOGGER_FACTORY = + new InternalFactory() { + public Logger create(InternalContext context) { + Member member = context.getExternalContext().getMember(); + return member == null ? Logger.getAnonymousLogger() + : Logger.getLogger(member.getDeclaringClass().getName()); + } + }; + + /** + * Constructs a new builder. + */ + public ContainerBuilder() { + // In the current container as the default Container implementation. + factories.put(Key.newInstance(Container.class, Container.DEFAULT_NAME), + CONTAINER_FACTORY); + + // Inject the logger for the injected member's declaring class. + factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME), + LOGGER_FACTORY); + } + + /** + * Maps a dependency. All methods in this class ultimately funnel through + * here. + */ + private ContainerBuilder factory(final Key key, + InternalFactory factory, Scope scope) { + ensureNotCreated(); + checkKey(key); + final InternalFactory scopedFactory = + scope.scopeFactory(key.getType(), key.getName(), factory); + factories.put(key, scopedFactory); + if (scope == Scope.SINGLETON) { + singletonFactories.add(new InternalFactory() { + public T create(InternalContext context) { + try { + context.setExternalContext(ExternalContext.newInstance( + null, key, context.getContainerImpl())); + return scopedFactory.create(context); + } finally { + context.setExternalContext(null); + } + } + }); + } + return this; + } + + /** + * Ensures a key isn't already mapped. + */ + private void checkKey(Key key) { + if (factories.containsKey(key) && !allowDuplicates) { + throw new DependencyException( + "Dependency mapping for " + key + " already exists."); + } + } + + /** + * Maps a factory to a given dependency type and name. + * + * @param type of dependency + * @param name of dependency + * @param factory creates objects to inject + * @param scope scope of injected instances + * @return this builder + */ + public ContainerBuilder factory(final Class type, final String name, + final Factory factory, Scope scope) { + InternalFactory internalFactory = + new InternalFactory() { + + public T create(InternalContext context) { + try { + Context externalContext = context.getExternalContext(); + return factory.create(externalContext); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return new LinkedHashMap() {{ + put("type", type); + put("name", name); + put("factory", factory); + }}.toString(); + } + }; + + return factory(Key.newInstance(type, name), internalFactory, scope); + } + + /** + * Convenience method. Equivalent to {@code factory(type, + * Container.DEFAULT_NAME, factory, scope)}. + * + * @see #factory(Class, String, Factory, Scope) + */ + public ContainerBuilder factory(Class type, + Factory factory, Scope scope) { + return factory(type, Container.DEFAULT_NAME, factory, scope); + } + + /** + * Convenience method. Equivalent to {@code factory(type, name, factory, + * Scope.DEFAULT)}. + * + * @see #factory(Class, String, Factory, Scope) + */ + public ContainerBuilder factory(Class type, String name, + Factory factory) { + return factory(type, name, factory, Scope.DEFAULT); + } + + /** + * Convenience method. Equivalent to {@code factory(type, + * Container.DEFAULT_NAME, factory, Scope.DEFAULT)}. + * + * @see #factory(Class, String, Factory, Scope) + */ + public ContainerBuilder factory(Class type, + Factory factory) { + return factory(type, Container.DEFAULT_NAME, factory, Scope.DEFAULT); + } + + /** + * Maps an implementation class to a given dependency type and name. Creates + * instances using the container, recursively injecting dependencies. + * + * @param type of dependency + * @param name of dependency + * @param implementation class + * @param scope scope of injected instances + * @return this builder + */ + public ContainerBuilder factory(final Class type, final String name, + final Class implementation, final Scope scope) { + // This factory creates new instances of the given implementation. + // We have to lazy load the constructor because the Container + // hasn't been created yet. + InternalFactory factory = new InternalFactory() { + + volatile ContainerImpl.ConstructorInjector constructor; + + @SuppressWarnings("unchecked") + public T create(InternalContext context) { + if (constructor == null) { + this.constructor = + context.getContainerImpl().getConstructor(implementation); + } + return (T) constructor.construct(context, type); + } + + @Override + public String toString() { + return new LinkedHashMap() {{ + put("type", type); + put("name", name); + put("implementation", implementation); + put("scope", scope); + }}.toString(); + } + }; + + return factory(Key.newInstance(type, name), factory, scope); + } + + /** + * Maps an implementation class to a given dependency type and name. Creates + * instances using the container, recursively injecting dependencies. + * + *

Sets scope to value from {@link Scoped} annotation on the + * implementation class. Defaults to {@link Scope#DEFAULT} if no annotation + * is found. + * + * @param type of dependency + * @param name of dependency + * @param implementation class + * @return this builder + */ + public ContainerBuilder factory(final Class type, String name, + final Class implementation) { + Scoped scoped = implementation.getAnnotation(Scoped.class); + Scope scope = scoped == null ? Scope.DEFAULT : scoped.value(); + return factory(type, name, implementation, scope); + } + + /** + * Convenience method. Equivalent to {@code factory(type, + * Container.DEFAULT_NAME, implementation)}. + * + * @see #factory(Class, String, Class) + */ + public ContainerBuilder factory(Class type, + Class implementation) { + return factory(type, Container.DEFAULT_NAME, implementation); + } + + /** + * Convenience method. Equivalent to {@code factory(type, + * Container.DEFAULT_NAME, type)}. + * + * @see #factory(Class, String, Class) + */ + public ContainerBuilder factory(Class type) { + return factory(type, Container.DEFAULT_NAME, type); + } + + /** + * Convenience method. Equivalent to {@code factory(type, name, type)}. + * + * @see #factory(Class, String, Class) + */ + public ContainerBuilder factory(Class type, String name) { + return factory(type, name, type); + } + + /** + * Convenience method. Equivalent to {@code factory(type, + * Container.DEFAULT_NAME, implementation, scope)}. + * + * @see #factory(Class, String, Class, Scope) + */ + public ContainerBuilder factory(Class type, + Class implementation, Scope scope) { + return factory(type, Container.DEFAULT_NAME, implementation, scope); + } + + /** + * Convenience method. Equivalent to {@code factory(type, + * Container.DEFAULT_NAME, type, scope)}. + * + * @see #factory(Class, String, Class, Scope) + */ + public ContainerBuilder factory(Class type, Scope scope) { + return factory(type, Container.DEFAULT_NAME, type, scope); + } + + /** + * Convenience method. Equivalent to {@code factory(type, name, type, + * scope)}. + * + * @see #factory(Class, String, Class, Scope) + */ + public ContainerBuilder factory(Class type, String name, Scope scope) { + return factory(type, name, type, scope); + } + + /** + * Convenience method. Equivalent to {@code alias(type, Container.DEFAULT_NAME, + * type)}. + * + * @see #alias(Class, String, String) + */ + public ContainerBuilder alias(Class type, String alias) { + return alias(type, Container.DEFAULT_NAME, alias); + } + + /** + * Maps an existing factory to a new name. + * + * @param type of dependency + * @param name of dependency + * @param alias of to the dependency + * @return this builder + */ + public ContainerBuilder alias(Class type, String name, String alias) { + return alias(Key.newInstance(type, name), Key.newInstance(type, alias)); + } + + /** + * Maps an existing dependency. All methods in this class ultimately funnel through + * here. + */ + private ContainerBuilder alias(final Key key, + final Key aliasKey) { + ensureNotCreated(); + checkKey(aliasKey); + + final InternalFactory scopedFactory = + (InternalFactory)factories.get(key); + if (scopedFactory == null) { + throw new DependencyException( + "Dependency mapping for " + key + " doesn't exists."); + } + factories.put(aliasKey, scopedFactory); + return this; + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, String value) { + return constant(String.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, int value) { + return constant(int.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, long value) { + return constant(long.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, boolean value) { + return constant(boolean.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, double value) { + return constant(double.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, float value) { + return constant(float.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, short value) { + return constant(short.class, name, value); + } + + /** + * Maps a constant value to the given name. + */ + public ContainerBuilder constant(String name, char value) { + return constant(char.class, name, value); + } + + /** + * Maps a class to the given name. + */ + public ContainerBuilder constant(String name, Class value) { + return constant(Class.class, name, value); + } + + /** + * Maps an enum to the given name. + */ + public > ContainerBuilder constant(String name, E value) { + return constant(value.getDeclaringClass(), name, value); + } + + /** + * Maps a constant value to the given type and name. + */ + private ContainerBuilder constant(final Class type, final String name, + final T value) { + InternalFactory factory = new InternalFactory() { + public T create(InternalContext ignored) { + return value; + } + + @Override + public String toString() { + return new LinkedHashMap() { + { + put("type", type); + put("name", name); + put("value", value); + } + }.toString(); + } + }; + + return factory(Key.newInstance(type, name), factory, Scope.DEFAULT); + } + + /** + * Upon creation, the {@link Container} will inject static fields and methods + * into the given classes. + * + * @param types for which static members will be injected + */ + public ContainerBuilder injectStatics(Class... types) { + staticInjections.addAll(Arrays.asList(types)); + return this; + } + + /** + * Returns true if this builder contains a mapping for the given type and + * name. + */ + public boolean contains(Class type, String name) { + return factories.containsKey(Key.newInstance(type, name)); + } + + /** + * Convenience method. Equivalent to {@code contains(type, + * Container.DEFAULT_NAME)}. + */ + public boolean contains(Class type) { + return contains(type, Container.DEFAULT_NAME); + } + + /** + * Creates a {@link Container} instance. Injects static members for classes + * which were registered using {@link #injectStatics(Class...)}. + * + * @param loadSingletons If true, the container will load all singletons + * now. If false, the container will lazily load singletons. Eager loading + * is appropriate for production use while lazy loading can speed + * development. + * @throws IllegalStateException if called more than once + */ + public Container create(boolean loadSingletons) { + ensureNotCreated(); + created = true; + final ContainerImpl container = new ContainerImpl( + new HashMap, InternalFactory>(factories)); + if (loadSingletons) { + container.callInContext(new ContainerImpl.ContextualCallable() { + public Void call(InternalContext context) { + for (InternalFactory factory : singletonFactories) { + factory.create(context); + } + return null; + } + }); + } + container.injectStatics(staticInjections); + return container; + } + + /** + * Currently we only support creating one Container instance per builder. + * If we want to support creating more than one container per builder, + * we should move to a "factory factory" model where we create a factory + * instance per Container. Right now, one factory instance would be + * shared across all the containers, singletons synchronize on the + * container when lazy loading, etc. + */ + private void ensureNotCreated() { + if (created) { + throw new IllegalStateException("Container already created."); + } + } + + public void setAllowDuplicates(boolean val) { + allowDuplicates = val; + } + + /** + * Implemented by classes which participate in building a container. + */ + public interface Command { + + /** + * Contributes factories to the given builder. + * + * @param builder + */ + void build(ContainerBuilder builder); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java new file mode 100644 index 000000000..f1cba9ff7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java @@ -0,0 +1,621 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import com.opensymphony.xwork2.inject.util.ReferenceCache; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.*; +import java.util.Map.Entry; +import java.security.AccessControlException; + +/** + * Default {@link Container} implementation. + * + * @see ContainerBuilder + * @author crazybob@google.com (Bob Lee) + */ +class ContainerImpl implements Container { + + final Map, InternalFactory> factories; + final Map,Set> factoryNamesByType; + + ContainerImpl(Map, InternalFactory> factories) { + this.factories = factories; + Map,Set> map = new HashMap,Set>(); + for (Key key : factories.keySet()) { + Set names = map.get(key.getType()); + if (names == null) { + names = new HashSet(); + map.put(key.getType(), names); + } + names.add(key.getName()); + } + + for (Entry,Set> entry : map.entrySet()) { + entry.setValue(Collections.unmodifiableSet(entry.getValue())); + } + + this.factoryNamesByType = Collections.unmodifiableMap(map); + } + + @SuppressWarnings("unchecked") + InternalFactory getFactory(Key key) { + return (InternalFactory) factories.get(key); + } + + /** + * Field and method injectors. + */ + final Map, List> injectors = + new ReferenceCache, List>() { + @Override + protected List create(Class key) { + List injectors = new ArrayList(); + addInjectors(key, injectors); + return injectors; + } + }; + + /** + * Recursively adds injectors for fields and methods from the given class to + * the given list. Injects parent classes before sub classes. + */ + void addInjectors(Class clazz, List injectors) { + if (clazz == Object.class) { + return; + } + + // Add injectors for superclass first. + addInjectors(clazz.getSuperclass(), injectors); + + // TODO (crazybob): Filter out overridden members. + addInjectorsForFields(clazz.getDeclaredFields(), false, injectors); + addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors); + } + + void injectStatics(List> staticInjections) { + final List injectors = new ArrayList(); + + for (Class clazz : staticInjections) { + addInjectorsForFields(clazz.getDeclaredFields(), true, injectors); + addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors); + } + + callInContext(new ContextualCallable() { + public Void call(InternalContext context) { + for (Injector injector : injectors) { + injector.inject(context, null); + } + return null; + } + }); + } + + void addInjectorsForMethods(Method[] methods, boolean statics, + List injectors) { + addInjectorsForMembers(Arrays.asList(methods), statics, injectors, + new InjectorFactory() { + public Injector create(ContainerImpl container, Method method, + String name) throws MissingDependencyException { + return new MethodInjector(container, method, name); + } + }); + } + + void addInjectorsForFields(Field[] fields, boolean statics, + List injectors) { + addInjectorsForMembers(Arrays.asList(fields), statics, injectors, + new InjectorFactory() { + public Injector create(ContainerImpl container, Field field, + String name) throws MissingDependencyException { + return new FieldInjector(container, field, name); + } + }); + } + + void addInjectorsForMembers( + List members, boolean statics, List injectors, + InjectorFactory injectorFactory) { + for (M member : members) { + if (isStatic(member) == statics) { + Inject inject = member.getAnnotation(Inject.class); + if (inject != null) { + try { + injectors.add(injectorFactory.create(this, member, inject.value())); + } catch (MissingDependencyException e) { + if (inject.required()) { + throw new DependencyException(e); + } + } + } + } + } + } + + interface InjectorFactory { + Injector create(ContainerImpl container, M member, String name) + throws MissingDependencyException; + } + + private boolean isStatic(Member member) { + return Modifier.isStatic(member.getModifiers()); + } + + static class FieldInjector implements Injector { + + final Field field; + final InternalFactory factory; + final ExternalContext externalContext; + + public FieldInjector(ContainerImpl container, Field field, String name) + throws MissingDependencyException { + this.field = field; + if (!field.isAccessible()) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) sm.checkPermission(new ReflectPermission("suppressAccessChecks")); + field.setAccessible(true); + } catch(AccessControlException e) { + throw new DependencyException("Security manager in use, could not access field: " + + field.getDeclaringClass().getName() + "(" + field.getName() + ")", e); + } + } + + Key key = Key.newInstance(field.getType(), name); + factory = container.getFactory(key); + if (factory == null) { + throw new MissingDependencyException( + "No mapping found for dependency " + key + " in " + field + "."); + } + + this.externalContext = ExternalContext.newInstance(field, key, container); + } + + public void inject(InternalContext context, Object o) { + ExternalContext previous = context.getExternalContext(); + context.setExternalContext(externalContext); + try { + field.set(o, factory.create(context)); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } finally { + context.setExternalContext(previous); + } + } + } + + /** + * Gets parameter injectors. + * + * @param member to which the parameters belong + * @param annotations on the parameters + * @param parameterTypes parameter types + * @return injections + */ + ParameterInjector[] + getParametersInjectors(M member, + Annotation[][] annotations, Class[] parameterTypes, String defaultName) + throws MissingDependencyException { + List> parameterInjectors = + new ArrayList>(); + + Iterator annotationsIterator = + Arrays.asList(annotations).iterator(); + for (Class parameterType : parameterTypes) { + Inject annotation = findInject(annotationsIterator.next()); + String name = annotation == null ? defaultName : annotation.value(); + Key key = Key.newInstance(parameterType, name); + parameterInjectors.add(createParameterInjector(key, member)); + } + + return toArray(parameterInjectors); + } + + ParameterInjector createParameterInjector( + Key key, Member member) throws MissingDependencyException { + InternalFactory factory = getFactory(key); + if (factory == null) { + throw new MissingDependencyException( + "No mapping found for dependency " + key + " in " + member + "."); + } + + ExternalContext externalContext = + ExternalContext.newInstance(member, key, this); + return new ParameterInjector(externalContext, factory); + } + + @SuppressWarnings("unchecked") + private ParameterInjector[] toArray( + List> parameterInjections) { + return parameterInjections.toArray( + new ParameterInjector[parameterInjections.size()]); + } + + /** + * Finds the {@link Inject} annotation in an array of annotations. + */ + Inject findInject(Annotation[] annotations) { + for (Annotation annotation : annotations) { + if (annotation.annotationType() == Inject.class) { + return Inject.class.cast(annotation); + } + } + return null; + } + + static class MethodInjector implements Injector { + + final Method method; + final ParameterInjector[] parameterInjectors; + + public MethodInjector(ContainerImpl container, Method method, String name) + throws MissingDependencyException { + this.method = method; + if (!method.isAccessible()) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) sm.checkPermission(new ReflectPermission("suppressAccessChecks")); + method.setAccessible(true); + } catch(AccessControlException e) { + throw new DependencyException("Security manager in use, could not access method: " + + name + "(" + method.getName() + ")", e); + } + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length == 0) { + throw new DependencyException( + method + " has no parameters to inject."); + } + parameterInjectors = container.getParametersInjectors( + method, method.getParameterAnnotations(), parameterTypes, name); + } + + public void inject(InternalContext context, Object o) { + try { + method.invoke(o, getParameters(method, context, parameterInjectors)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + Map, ConstructorInjector> constructors = + new ReferenceCache, ConstructorInjector>() { + @Override + @SuppressWarnings("unchecked") + protected ConstructorInjector create(Class implementation) { + return new ConstructorInjector(ContainerImpl.this, implementation); + } + }; + + static class ConstructorInjector { + + final Class implementation; + final List injectors; + final Constructor constructor; + final ParameterInjector[] parameterInjectors; + + ConstructorInjector(ContainerImpl container, Class implementation) { + this.implementation = implementation; + + constructor = findConstructorIn(implementation); + if (!constructor.isAccessible()) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) sm.checkPermission(new ReflectPermission("suppressAccessChecks")); + constructor.setAccessible(true); + } catch(AccessControlException e) { + throw new DependencyException("Security manager in use, could not access constructor: " + + implementation.getName() + "(" + constructor.getName() + ")", e); + } + } + + MissingDependencyException exception = null; + Inject inject = null; + ParameterInjector[] parameters = null; + + try { + inject = constructor.getAnnotation(Inject.class); + parameters = constructParameterInjector(inject, container, constructor); + } catch (MissingDependencyException e) { + exception = e; + } + parameterInjectors = parameters; + + if ( exception != null) { + if ( inject != null && inject.required()) { + throw new DependencyException(exception); + } + } + injectors = container.injectors.get(implementation); + } + + ParameterInjector[] constructParameterInjector( + Inject inject, ContainerImpl container, Constructor constructor) throws MissingDependencyException{ + return constructor.getParameterTypes().length == 0 + ? null // default constructor. + : container.getParametersInjectors( + constructor, + constructor.getParameterAnnotations(), + constructor.getParameterTypes(), + inject.value() + ); + } + + @SuppressWarnings("unchecked") + private Constructor findConstructorIn(Class implementation) { + Constructor found = null; + Constructor[] declaredConstructors = (Constructor[]) implementation + .getDeclaredConstructors(); + for(Constructor constructor : declaredConstructors) { + if (constructor.getAnnotation(Inject.class) != null) { + if (found != null) { + throw new DependencyException("More than one constructor annotated" + + " with @Inject found in " + implementation + "."); + } + found = constructor; + } + } + if (found != null) { + return found; + } + + // If no annotated constructor is found, look for a no-arg constructor + // instead. + try { + return implementation.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new DependencyException("Could not find a suitable constructor" + + " in " + implementation.getName() + "."); + } + } + + /** + * Construct an instance. Returns {@code Object} instead of {@code T} + * because it may return a proxy. + */ + Object construct(InternalContext context, Class expectedType) { + ConstructionContext constructionContext = + context.getConstructionContext(this); + + // We have a circular reference between constructors. Return a proxy. + if (constructionContext.isConstructing()) { + // TODO (crazybob): if we can't proxy this object, can we proxy the + // other object? + return constructionContext.createProxy(expectedType); + } + + // If we're re-entering this factory while injecting fields or methods, + // return the same instance. This prevents infinite loops. + T t = constructionContext.getCurrentReference(); + if (t != null) { + return t; + } + + try { + // First time through... + constructionContext.startConstruction(); + try { + Object[] parameters = + getParameters(constructor, context, parameterInjectors); + t = constructor.newInstance(parameters); + constructionContext.setProxyDelegates(t); + } finally { + constructionContext.finishConstruction(); + } + + // Store reference. If an injector re-enters this factory, they'll + // get the same reference. + constructionContext.setCurrentReference(t); + + // Inject fields and methods. + for (Injector injector : injectors) { + injector.inject(context, t); + } + + return t; + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } finally { + constructionContext.removeCurrentReference(); + } + } + } + + static class ParameterInjector { + + final ExternalContext externalContext; + final InternalFactory factory; + + public ParameterInjector(ExternalContext externalContext, + InternalFactory factory) { + this.externalContext = externalContext; + this.factory = factory; + } + + T inject(Member member, InternalContext context) { + ExternalContext previous = context.getExternalContext(); + context.setExternalContext(externalContext); + try { + return factory.create(context); + } finally { + context.setExternalContext(previous); + } + } + } + + private static Object[] getParameters(Member member, InternalContext context, + ParameterInjector[] parameterInjectors) { + if (parameterInjectors == null) { + return null; + } + + Object[] parameters = new Object[parameterInjectors.length]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = parameterInjectors[i].inject(member, context); + } + return parameters; + } + + void inject(Object o, InternalContext context) { + List injectors = this.injectors.get(o.getClass()); + for (Injector injector : injectors) { + injector.inject(context, o); + } + } + + T inject(Class implementation, InternalContext context) { + try { + ConstructorInjector constructor = getConstructor(implementation); + return implementation.cast( + constructor.construct(context, implementation)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + T getInstance(Class type, String name, InternalContext context) { + ExternalContext previous = context.getExternalContext(); + Key key = Key.newInstance(type, name); + context.setExternalContext(ExternalContext.newInstance(null, key, this)); + try { + InternalFactory o = getFactory(key); + if (o != null) { + return getFactory(key).create(context); + } else { + return null; + } + } finally { + context.setExternalContext(previous); + } + } + + T getInstance(Class type, InternalContext context) { + return getInstance(type, DEFAULT_NAME, context); + } + + public void inject(final Object o) { + callInContext(new ContextualCallable() { + public Void call(InternalContext context) { + inject(o, context); + return null; + } + }); + } + + public T inject(final Class implementation) { + return callInContext(new ContextualCallable() { + public T call(InternalContext context) { + return inject(implementation, context); + } + }); + } + + public T getInstance(final Class type, final String name) { + return callInContext(new ContextualCallable() { + public T call(InternalContext context) { + return getInstance(type, name, context); + } + }); + } + + public T getInstance(final Class type) { + return callInContext(new ContextualCallable() { + public T call(InternalContext context) { + return getInstance(type, context); + } + }); + } + + public Set getInstanceNames(final Class type) { + return factoryNamesByType.get(type); + } + + ThreadLocal localContext = + new ThreadLocal() { + @Override + protected Object[] initialValue() { + return new Object[1]; + } + }; + + /** + * Looks up thread local context. Creates (and removes) a new context if + * necessary. + */ + T callInContext(ContextualCallable callable) { + Object[] reference = localContext.get(); + if (reference[0] == null) { + reference[0] = new InternalContext(this); + try { + return callable.call((InternalContext)reference[0]); + } finally { + // Only remove the context if this call created it. + reference[0] = null; + } + } else { + // Someone else will clean up this context. + return callable.call((InternalContext)reference[0]); + } + } + + interface ContextualCallable { + T call(InternalContext context); + } + + /** + * Gets a constructor function for a given implementation class. + */ + @SuppressWarnings("unchecked") + ConstructorInjector getConstructor(Class implementation) { + return constructors.get(implementation); + } + + final ThreadLocal localScopeStrategy = + new ThreadLocal(); + + public void setScopeStrategy(Scope.Strategy scopeStrategy) { + this.localScopeStrategy.set(scopeStrategy); + } + + public void removeScopeStrategy() { + this.localScopeStrategy.remove(); + } + + /** + * Injects a field or method in a given object. + */ + interface Injector extends Serializable { + void inject(InternalContext context, Object o); + } + + static class MissingDependencyException extends Exception { + + MissingDependencyException(String message) { + super(message); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Context.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Context.java new file mode 100644 index 000000000..233b5e478 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Context.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.lang.reflect.Member; + +/** + * Context of the current injection. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface Context { + + /** + * Gets the {@link Container}. + */ + Container getContainer(); + + /** + * Gets the current scope strategy. See {@link + * Container#setScopeStrategy(Scope.Strategy)}. + * + * @throws IllegalStateException if no strategy has been set + */ + Scope.Strategy getScopeStrategy(); + + /** + * Gets the field, method or constructor which is being injected. Returns + * {@code null} if the object currently being constructed is pre-loaded as + * a singleton or requested from {@link Container#getInstance(Class)}. + */ + Member getMember(); + + /** + * Gets the type of the field or parameter which is being injected. + */ + Class getType(); + + /** + * Gets the name of the injection specified by {@link Inject#value()}. + */ + String getName(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java new file mode 100644 index 000000000..f92896def --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +/** + * Thrown when a dependency is misconfigured. + * + * @author crazybob@google.com (Bob Lee) + */ +public class DependencyException extends RuntimeException { + + public DependencyException(String message) { + super(message); + } + + public DependencyException(String message, Throwable cause) { + super(message, cause); + } + + public DependencyException(Throwable cause) { + super(cause); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java new file mode 100644 index 000000000..8a3880e1b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.lang.reflect.Member; +import java.util.LinkedHashMap; + +/** + * An immutable snapshot of the current context which is safe to + * expose to client code. + * + * @author crazybob@google.com (Bob Lee) + */ +class ExternalContext implements Context { + + final Member member; + final Key key; + final ContainerImpl container; + + public ExternalContext(Member member, Key key, ContainerImpl container) { + this.member = member; + this.key = key; + this.container = container; + } + + public Class getType() { + return key.getType(); + } + + public Scope.Strategy getScopeStrategy() { + return (Scope.Strategy) container.localScopeStrategy.get(); + } + + public Container getContainer() { + return container; + } + + public Member getMember() { + return member; + } + + public String getName() { + return key.getName(); + } + + @Override + public String toString() { + return "Context" + new LinkedHashMap() {{ + put("member", member); + put("type", getType()); + put("name", getName()); + put("container", container); + }}.toString(); + } + + static ExternalContext newInstance(Member member, Key key, + ContainerImpl container) { + return new ExternalContext(member, key, container); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Factory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Factory.java new file mode 100644 index 000000000..11175061e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Factory.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +/** + * A custom factory. Creates objects which will be injected. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface Factory { + + /** + * Creates an object to be injected. + * + * @param context of this injection + * @return instance to be injected + * @throws Exception if unable to create object + */ + T create(Context context) throws Exception; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Inject.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Inject.java new file mode 100644 index 000000000..610d2bb22 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Inject.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import static com.opensymphony.xwork2.inject.Container.DEFAULT_NAME; + +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + *

Annotates members and parameters which should have their value[s] + * injected. + * + * @author crazybob@google.com (Bob Lee) + */ +@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER}) +@Retention(RUNTIME) +public @interface Inject { + + /** + * Dependency name. Defaults to {@link Container#DEFAULT_NAME}. + */ + String value() default DEFAULT_NAME; + + /** + * Whether or not injection is required. Applicable only to methods and + * fields (not constructors or parameters). + */ + boolean required() default true; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java new file mode 100644 index 000000000..16fa0c130 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.util.HashMap; +import java.util.Map; + +/** + * Internal context. Used to coordinate injections and support circular + * dependencies. + * + * @author crazybob@google.com (Bob Lee) + */ +class InternalContext { + + final ContainerImpl container; + final Map> constructionContexts = + new HashMap>(); + Scope.Strategy scopeStrategy; + ExternalContext externalContext; + + InternalContext(ContainerImpl container) { + this.container = container; + } + + public Container getContainer() { + return container; + } + + ContainerImpl getContainerImpl() { + return container; + } + + Scope.Strategy getScopeStrategy() { + if (scopeStrategy == null) { + scopeStrategy = (Scope.Strategy) container.localScopeStrategy.get(); + + if (scopeStrategy == null) { + throw new IllegalStateException("Scope strategy not set. " + + "Please call Container.setScopeStrategy()."); + } + } + + return scopeStrategy; + } + + @SuppressWarnings("unchecked") + ConstructionContext getConstructionContext(Object key) { + ConstructionContext constructionContext = + (ConstructionContext) constructionContexts.get(key); + if (constructionContext == null) { + constructionContext = new ConstructionContext(); + constructionContexts.put(key, constructionContext); + } + return constructionContext; + } + + @SuppressWarnings("unchecked") + ExternalContext getExternalContext() { + return (ExternalContext) externalContext; + } + + void setExternalContext(ExternalContext externalContext) { + this.externalContext = externalContext; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java new file mode 100644 index 000000000..49fcf27e8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.io.Serializable; + +/** + * Creates objects which will be injected. + * + * @author crazybob@google.com (Bob Lee) + */ +interface InternalFactory extends Serializable { + + /** + * Creates an object to be injected. + * + * @param context of this injection + * @return instance to be injected + */ + T create(InternalContext context); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Key.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Key.java new file mode 100644 index 000000000..03756b9f6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Key.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +/** + * Dependency mapping key. Uniquely identified by the required type and name. + * + * @author crazybob@google.com (Bob Lee) + */ +class Key { + + final Class type; + final String name; + final int hashCode; + + private Key(Class type, String name) { + if (type == null) { + throw new NullPointerException("Type is null."); + } + if (name == null) { + throw new NullPointerException("Name is null."); + } + + this.type = type; + this.name = name; + + hashCode = type.hashCode() * 31 + name.hashCode(); + } + + Class getType() { + return type; + } + + String getName() { + return name; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Key)) { + return false; + } + if (o == this) { + return true; + } + Key other = (Key) o; + return name.equals(other.name) && type.equals(other.type); + } + + @Override + public String toString() { + return "[type=" + type.getName() + ", name='" + name + "']"; + } + + static Key newInstance(Class type, String name) { + return new Key(type, name); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Scope.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Scope.java new file mode 100644 index 000000000..b327db837 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Scope.java @@ -0,0 +1,217 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.util.concurrent.Callable; + +/** + * Scope of an injected objects. + * + * @author crazybob + */ +public enum Scope { + + /** + * One instance per injection. + */ + DEFAULT { + @Override + InternalFactory scopeFactory(Class type, String name, + InternalFactory factory) { + return factory; + } + }, + + /** + * One instance per container. + */ + SINGLETON { + @Override + InternalFactory scopeFactory(Class type, String name, + final InternalFactory factory) { + return new InternalFactory() { + T instance; + public T create(InternalContext context) { + synchronized (context.getContainer()) { + if (instance == null) { + instance = factory.create(context); + } + return instance; + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per thread. + * + *

Note: if a thread local object strongly references its {@link + * Container}, neither the {@code Container} nor the object will be + * eligible for garbage collection, i.e. memory leak. + */ + THREAD { + @Override + InternalFactory scopeFactory(Class type, String name, + final InternalFactory factory) { + return new InternalFactory() { + final ThreadLocal threadLocal = new ThreadLocal(); + public T create(final InternalContext context) { + T t = threadLocal.get(); + if (t == null) { + t = factory.create(context); + threadLocal.set(t); + } + return t; + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per request. + */ + REQUEST { + @Override + InternalFactory scopeFactory(final Class type, + final String name, final InternalFactory factory) { + return new InternalFactory() { + public T create(InternalContext context) { + Strategy strategy = context.getScopeStrategy(); + try { + return strategy.findInRequest( + type, name, toCallable(context, factory)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per session. + */ + SESSION { + @Override + InternalFactory scopeFactory(final Class type, + final String name, final InternalFactory factory) { + return new InternalFactory() { + public T create(InternalContext context) { + Strategy strategy = context.getScopeStrategy(); + try { + return strategy.findInSession( + type, name, toCallable(context, factory)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per wizard. + */ + WIZARD { + @Override + InternalFactory scopeFactory(final Class type, + final String name, final InternalFactory factory) { + return new InternalFactory() { + public T create(InternalContext context) { + Strategy strategy = context.getScopeStrategy(); + try { + return strategy.findInWizard( + type, name, toCallable(context, factory)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }; + + Callable toCallable(final InternalContext context, + final InternalFactory factory) { + return new Callable() { + public T call() throws Exception { + return factory.create(context); + } + }; + } + + /** + * Wraps factory with scoping logic. + */ + abstract InternalFactory scopeFactory( + Class type, String name, InternalFactory factory); + + /** + * Pluggable scoping strategy. Enables users to provide custom + * implementations of request, session, and wizard scopes. Implement and + * pass to {@link + * Container#setScopeStrategy(com.opensymphony.xwork2.inject.Scope.Strategy)}. + */ + public interface Strategy { + + /** + * Finds an object for the given type and name in the request scope. + * Creates a new object if necessary using the given factory. + */ + T findInRequest(Class type, String name, + Callable factory) throws Exception; + + /** + * Finds an object for the given type and name in the session scope. + * Creates a new object if necessary using the given factory. + */ + T findInSession(Class type, String name, + Callable factory) throws Exception; + + /** + * Finds an object for the given type and name in the wizard scope. + * Creates a new object if necessary using the given factory. + */ + T findInWizard(Class type, String name, + Callable factory) throws Exception; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java new file mode 100644 index 000000000..31a9447ca --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates a scoped implementation class. + * + * @author crazybob + */ +@Target(ElementType.TYPE) +@Retention(RUNTIME) +public @interface Scoped { + + /** + * Scope. + */ + Scope value(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/package-info.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/package-info.java new file mode 100644 index 000000000..6e26e24c5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/package-info.java @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Guice (pronounced "juice"). A lightweight dependency injection + * container. Features include: + * + *

    + *
  • constructor, method, and field injection
  • + *
  • static method and field injection
  • + *
  • circular reference support (including constructors if you depend upon + * interfaces)
  • + *
  • high performance
  • + *
  • externalize what needs to be and no more
  • + *
+ */ +package com.opensymphony.xwork2.inject; diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java new file mode 100644 index 000000000..869b96bfb --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +import java.lang.ref.PhantomReference; + +/** + * Phantom reference with a {@link com.opensymphony.xwork2.inject.util.FinalizableReference#finalizeReferent() finalizeReferent()} method which a + * background thread invokes after the garbage collector reclaims the + * referent. This is a simpler alternative to using a {@link + * java.lang.ref.ReferenceQueue}. + * + * @author crazybob@google.com (Bob Lee) + */ +public abstract class FinalizablePhantomReference + extends PhantomReference implements FinalizableReference { + + protected FinalizablePhantomReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java new file mode 100644 index 000000000..16653e2d6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +/** + * Package-private interface implemented by references that have code to run + * after garbage collection of their referents. + * + * @author crazybob@google.com (Bob Lee) + */ +interface FinalizableReference { + + /** + * Invoked on a background thread after the referent has been garbage + * collected. + */ + void finalizeReferent(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java new file mode 100644 index 000000000..72121efba --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Starts a background thread that cleans up after reclaimed referents. + * + * @author Bob Lee (crazybob@google.com) + */ +class FinalizableReferenceQueue extends ReferenceQueue { + + private static final Logger logger = + Logger.getLogger(FinalizableReferenceQueue.class.getName()); + + private FinalizableReferenceQueue() {} + + void cleanUp(Reference reference) { + try { + ((FinalizableReference) reference).finalizeReferent(); + } catch (Throwable t) { + deliverBadNews(t); + } + } + + void deliverBadNews(Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + + void start() { + Thread thread = new Thread("FinalizableReferenceQueue") { + @Override + public void run() { + while (true) { + try { + cleanUp(remove()); + } catch (InterruptedException e) { /* ignore */ } + } + } + }; + thread.setDaemon(true); + thread.start(); + } + + static ReferenceQueue instance = createAndStart(); + + static FinalizableReferenceQueue createAndStart() { + FinalizableReferenceQueue queue = new FinalizableReferenceQueue(); + queue.start(); + return queue; + } + + /** + * Gets instance. + */ + public static ReferenceQueue getInstance() { + return instance; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java new file mode 100644 index 000000000..eeb1c2064 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +import java.lang.ref.SoftReference; + +/** + * Soft reference with a {@link com.opensymphony.xwork2.inject.util.FinalizableReference#finalizeReferent() finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author crazybob@google.com (Bob Lee) + */ +public abstract class FinalizableSoftReference extends SoftReference + implements FinalizableReference { + + protected FinalizableSoftReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java new file mode 100644 index 000000000..7c3a67faa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +import java.lang.ref.WeakReference; + +/** + * Weak reference with a {@link com.opensymphony.xwork2.inject.util.FinalizableReference#finalizeReferent() finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author crazybob@google.com (Bob Lee) + */ +public abstract class FinalizableWeakReference extends WeakReference + implements FinalizableReference { + + protected FinalizableWeakReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java new file mode 100644 index 000000000..fd212373e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +/** + * A Function provides a transformation on an object and returns the resulting + * object. For example, a {@code StringToIntegerFunction} may implement + * Function<String,Integer> and transform integers in String + * format to Integer format. + * + *

The transformation on the source object does not necessarily result in + * an object of a different type. For example, a + * {@code FarenheitToCelciusFunction} may implement + * Function<Float,Float>. + * + *

Implementors of Function which may cause side effects upon evaluation are + * strongly encouraged to state this fact clearly in their API documentation. + */ +public interface Function { + + /** + * Applies the function to an object of type {@code F}, resulting in an object + * of type {@code T}. Note that types {@code F} and {@code T} may or may not + * be the same. + * + * @param from The source object. + * @return The resulting object. + */ + T apply(F from); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java new file mode 100644 index 000000000..166742cfa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java @@ -0,0 +1,184 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +import static com.opensymphony.xwork2.inject.util.ReferenceType.STRONG; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.concurrent.*; + +/** + * Extends {@link ReferenceMap} to support lazy loading values by overriding + * {@link #create(Object)}. + * + * @author crazybob@google.com (Bob Lee) + */ +public abstract class ReferenceCache extends ReferenceMap { + + private static final long serialVersionUID = 0; + + transient ConcurrentMap> futures = + new ConcurrentHashMap>(); + + transient ThreadLocal> localFuture = new ThreadLocal>(); + + public ReferenceCache(ReferenceType keyReferenceType, + ReferenceType valueReferenceType) { + super(keyReferenceType, valueReferenceType); + } + + /** + * Equivalent to {@code new ReferenceCache(STRONG, STRONG)}. + */ + public ReferenceCache() { + super(STRONG, STRONG); + } + + /** + * Override to lazy load values. Use as an alternative to {@link + * #put(Object,Object)}. Invoked by getter if value isn't already cached. + * Must not return {@code null}. This method will not be called again until + * the garbage collector reclaims the returned value. + */ + protected abstract V create(K key); + + V internalCreate(K key) { + try { + FutureTask futureTask = new FutureTask( + new CallableCreate(key)); + + // use a reference so we get the same equality semantics. + Object keyReference = referenceKey(key); + Future future = futures.putIfAbsent(keyReference, futureTask); + if (future == null) { + // winning thread. + try { + if (localFuture.get() != null) { + throw new IllegalStateException( + "Nested creations within the same cache are not allowed."); + } + localFuture.set(futureTask); + futureTask.run(); + V value = futureTask.get(); + putStrategy().execute(this, + keyReference, referenceValue(keyReference, value)); + return value; + } finally { + localFuture.remove(); + futures.remove(keyReference); + } + } else { + // wait for winning thread. + return future.get(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } + throw new RuntimeException(cause); + } + } + + /** + * {@inheritDoc} + * + * If this map does not contain an entry for the given key and {@link + * #create(Object)} has been overridden, this method will create a new + * value, put it in the map, and return it. + * + * @throws NullPointerException if {@link #create(Object)} returns null. + * @throws java.util.concurrent.CancellationException if the creation is + * cancelled. See {@link #cancel()}. + */ + @SuppressWarnings("unchecked") + @Override public V get(final Object key) { + V value = super.get(key); + return (value == null) + ? internalCreate((K) key) + : value; + } + + /** + * Cancels the current {@link #create(Object)}. Throws {@link + * java.util.concurrent.CancellationException} to all clients currently + * blocked on {@link #get(Object)}. + */ + protected void cancel() { + Future future = localFuture.get(); + if (future == null) { + throw new IllegalStateException("Not in create()."); + } + future.cancel(false); + } + + class CallableCreate implements Callable { + + K key; + + public CallableCreate(K key) { + this.key = key; + } + + public V call() { + // try one more time (a previous future could have come and gone.) + V value = internalGet(key); + if (value != null) { + return value; + } + + // create value. + value = create(key); + if (value == null) { + throw new NullPointerException( + "create(K) returned null for: " + key); + } + return value; + } + } + + /** + * Returns a {@code ReferenceCache} delegating to the specified {@code + * function}. The specified function must not return {@code null}. + */ + public static ReferenceCache of( + ReferenceType keyReferenceType, + ReferenceType valueReferenceType, + final Function function) { + ensureNotNull(function); + return new ReferenceCache(keyReferenceType, valueReferenceType) { + @Override + protected V create(K key) { + return function.apply(key); + } + private static final long serialVersionUID = 0; + }; + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + this.futures = new ConcurrentHashMap>(); + this.localFuture = new ThreadLocal>(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java new file mode 100644 index 000000000..2542a8c96 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java @@ -0,0 +1,616 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +import static com.opensymphony.xwork2.inject.util.ReferenceType.STRONG; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.Reference; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Concurrent hash map that wraps keys and/or values in soft or weak + * references. Does not support null keys or values. Uses identity equality + * for weak and soft keys. + * + *

The concurrent semantics of {@link ConcurrentHashMap} combined with the + * fact that the garbage collector can asynchronously reclaim and clean up + * after keys and values at any time can lead to some racy semantics. For + * example, {@link #size()} returns an upper bound on the size, i.e. the actual + * size may be smaller in cases where the key or value has been reclaimed but + * the map entry has not been cleaned up yet. + * + *

Another example: If {@link #get(Object)} cannot find an existing entry + * for a key, it will try to create one. This operation is not atomic. One + * thread could {@link #put(Object, Object)} a value between the time another + * thread running {@code get()} checks for an entry and decides to create one. + * In this case, the newly created value will replace the put value in the + * map. Also, two threads running {@code get()} concurrently can potentially + * create duplicate values for a given key. + * + *

In other words, this class is great for caching but not atomicity. + * + * @author crazybob@google.com (Bob Lee) + */ +@SuppressWarnings("unchecked") +public class ReferenceMap implements Map, Serializable { + + private static final long serialVersionUID = 0; + + transient ConcurrentMap delegate; + + final ReferenceType keyReferenceType; + final ReferenceType valueReferenceType; + + /** + * Concurrent hash map that wraps keys and/or values based on specified + * reference types. + * + * @param keyReferenceType key reference type + * @param valueReferenceType value reference type + */ + public ReferenceMap(ReferenceType keyReferenceType, + ReferenceType valueReferenceType) { + ensureNotNull(keyReferenceType, valueReferenceType); + + if (keyReferenceType == ReferenceType.PHANTOM + || valueReferenceType == ReferenceType.PHANTOM) { + throw new IllegalArgumentException("Phantom references not supported."); + } + + this.delegate = new ConcurrentHashMap(); + this.keyReferenceType = keyReferenceType; + this.valueReferenceType = valueReferenceType; + } + + V internalGet(K key) { + Object valueReference = delegate.get(makeKeyReferenceAware(key)); + return valueReference == null + ? null + : (V) dereferenceValue(valueReference); + } + + public V get(final Object key) { + ensureNotNull(key); + return internalGet((K) key); + } + + V execute(Strategy strategy, K key, V value) { + ensureNotNull(key, value); + Object keyReference = referenceKey(key); + Object valueReference = strategy.execute( + this, + keyReference, + referenceValue(keyReference, value) + ); + return valueReference == null ? null + : (V) dereferenceValue(valueReference); + } + + public V put(K key, V value) { + return execute(putStrategy(), key, value); + } + + public V remove(Object key) { + ensureNotNull(key); + Object referenceAwareKey = makeKeyReferenceAware(key); + Object valueReference = delegate.remove(referenceAwareKey); + return valueReference == null ? null + : (V) dereferenceValue(valueReference); + } + + public int size() { + return delegate.size(); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public boolean containsKey(Object key) { + ensureNotNull(key); + Object referenceAwareKey = makeKeyReferenceAware(key); + return delegate.containsKey(referenceAwareKey); + } + + public boolean containsValue(Object value) { + ensureNotNull(value); + for (Object valueReference : delegate.values()) { + if (value.equals(dereferenceValue(valueReference))) { + return true; + } + } + return false; + } + + public void putAll(Map t) { + for (Map.Entry entry : t.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + public void clear() { + delegate.clear(); + } + + /** + * Returns an unmodifiable set view of the keys in this map. As this method + * creates a defensive copy, the performance is O(n). + */ + public Set keySet() { + return Collections.unmodifiableSet( + dereferenceKeySet(delegate.keySet())); + } + + /** + * Returns an unmodifiable set view of the values in this map. As this + * method creates a defensive copy, the performance is O(n). + */ + public Collection values() { + return Collections.unmodifiableCollection( + dereferenceValues(delegate.values())); + } + + public V putIfAbsent(K key, V value) { + // TODO (crazybob) if the value has been gc'ed but the entry hasn't been + // cleaned up yet, this put will fail. + return execute(putIfAbsentStrategy(), key, value); + } + + public boolean remove(Object key, Object value) { + ensureNotNull(key, value); + Object referenceAwareKey = makeKeyReferenceAware(key); + Object referenceAwareValue = makeValueReferenceAware(value); + return delegate.remove(referenceAwareKey, referenceAwareValue); + } + + public boolean replace(K key, V oldValue, V newValue) { + ensureNotNull(key, oldValue, newValue); + Object keyReference = referenceKey(key); + + Object referenceAwareOldValue = makeValueReferenceAware(oldValue); + return delegate.replace( + keyReference, + referenceAwareOldValue, + referenceValue(keyReference, newValue) + ); + } + + public V replace(K key, V value) { + // TODO (crazybob) if the value has been gc'ed but the entry hasn't been + // cleaned up yet, this will succeed when it probably shouldn't. + return execute(replaceStrategy(), key, value); + } + + /** + * Returns an unmodifiable set view of the entries in this map. As this + * method creates a defensive copy, the performance is O(n). + */ + public Set> entrySet() { + Set> entrySet = new HashSet>(); + for (Map.Entry entry : delegate.entrySet()) { + Map.Entry dereferenced = dereferenceEntry(entry); + if (dereferenced != null) { + entrySet.add(dereferenced); + } + } + return Collections.unmodifiableSet(entrySet); + } + + /** + * Dereferences an entry. Returns null if the key or value has been gc'ed. + */ + Entry dereferenceEntry(Map.Entry entry) { + K key = dereferenceKey(entry.getKey()); + V value = dereferenceValue(entry.getValue()); + return (key == null || value == null) + ? null + : new Entry(key, value); + } + + /** + * Creates a reference for a key. + */ + Object referenceKey(K key) { + switch (keyReferenceType) { + case STRONG: return key; + case SOFT: return new SoftKeyReference(key); + case WEAK: return new WeakKeyReference(key); + default: throw new AssertionError(); + } + } + + /** + * Converts a reference to a key. + */ + K dereferenceKey(Object o) { + return (K) dereference(keyReferenceType, o); + } + + /** + * Converts a reference to a value. + */ + V dereferenceValue(Object o) { + return (V) dereference(valueReferenceType, o); + } + + /** + * Returns the refererent for reference given its reference type. + */ + Object dereference(ReferenceType referenceType, Object reference) { + return referenceType == STRONG ? reference : ((Reference) reference).get(); + } + + /** + * Creates a reference for a value. + */ + Object referenceValue(Object keyReference, Object value) { + switch (valueReferenceType) { + case STRONG: return value; + case SOFT: return new SoftValueReference(keyReference, value); + case WEAK: return new WeakValueReference(keyReference, value); + default: throw new AssertionError(); + } + } + + /** + * Dereferences a set of key references. + */ + Set dereferenceKeySet(Set keyReferences) { + return keyReferenceType == STRONG + ? keyReferences + : dereferenceCollection(keyReferenceType, keyReferences, new HashSet()); + } + + /** + * Dereferences a collection of value references. + */ + Collection dereferenceValues(Collection valueReferences) { + return valueReferenceType == STRONG + ? valueReferences + : dereferenceCollection(valueReferenceType, valueReferences, + new ArrayList(valueReferences.size())); + } + + /** + * Wraps key so it can be compared to a referenced key for equality. + */ + Object makeKeyReferenceAware(Object o) { + return keyReferenceType == STRONG ? o : new KeyReferenceAwareWrapper(o); + } + + /** + * Wraps value so it can be compared to a referenced value for equality. + */ + Object makeValueReferenceAware(Object o) { + return valueReferenceType == STRONG ? o : new ReferenceAwareWrapper(o); + } + + /** + * Dereferences elements in {@code in} using + * {@code referenceType} and puts them in {@code out}. Returns + * {@code out}. + */ + > T dereferenceCollection( + ReferenceType referenceType, T in, T out) { + for (Object reference : in) { + out.add(dereference(referenceType, reference)); + } + return out; + } + + /** + * Marker interface to differentiate external and internal references. + */ + interface InternalReference {} + + static int keyHashCode(Object key) { + return System.identityHashCode(key); + } + + /** + * Tests weak and soft references for identity equality. Compares references + * to other references and wrappers. If o is a reference, this returns true + * if r == o or if r and o reference the same non null object. If o is a + * wrapper, this returns true if r's referent is identical to the wrapped + * object. + */ + static boolean referenceEquals(Reference r, Object o) { + // compare reference to reference. + if (o instanceof InternalReference) { + // are they the same reference? used in cleanup. + if (o == r) { + return true; + } + + // do they reference identical values? used in conditional puts. + Object referent = ((Reference) o).get(); + return referent != null && referent == r.get(); + } + + // is the wrapped object identical to the referent? used in lookups. + return ((ReferenceAwareWrapper) o).unwrap() == r.get(); + } + + /** + * Big hack. Used to compare keys and values to referenced keys and values + * without creating more references. + */ + static class ReferenceAwareWrapper { + + Object wrapped; + + ReferenceAwareWrapper(Object wrapped) { + this.wrapped = wrapped; + } + + Object unwrap() { + return wrapped; + } + + @Override + public int hashCode() { + return wrapped.hashCode(); + } + + @Override + public boolean equals(Object obj) { + // defer to reference's equals() logic. + return obj.equals(this); + } + } + + /** + * Used for keys. Overrides hash code to use identity hash code. + */ + static class KeyReferenceAwareWrapper extends ReferenceAwareWrapper { + + public KeyReferenceAwareWrapper(Object wrapped) { + super(wrapped); + } + + @Override + public int hashCode() { + return System.identityHashCode(wrapped); + } + } + + class SoftKeyReference extends FinalizableSoftReference + implements InternalReference { + + int hashCode; + + public SoftKeyReference(Object key) { + super(key); + this.hashCode = keyHashCode(key); + } + + public void finalizeReferent() { + delegate.remove(this); + } + + @Override public int hashCode() { + return this.hashCode; + } + + @Override public boolean equals(Object o) { + return referenceEquals(this, o); + } + } + + class WeakKeyReference extends FinalizableWeakReference + implements InternalReference { + + int hashCode; + + public WeakKeyReference(Object key) { + super(key); + this.hashCode = keyHashCode(key); + } + + public void finalizeReferent() { + delegate.remove(this); + } + + @Override public int hashCode() { + return this.hashCode; + } + + @Override public boolean equals(Object o) { + return referenceEquals(this, o); + } + } + + class SoftValueReference extends FinalizableSoftReference + implements InternalReference { + + Object keyReference; + + public SoftValueReference(Object keyReference, Object value) { + super(value); + this.keyReference = keyReference; + } + + public void finalizeReferent() { + delegate.remove(keyReference, this); + } + + @Override public boolean equals(Object obj) { + return referenceEquals(this, obj); + } + } + + class WeakValueReference extends FinalizableWeakReference + implements InternalReference { + + Object keyReference; + + public WeakValueReference(Object keyReference, Object value) { + super(value); + this.keyReference = keyReference; + } + + public void finalizeReferent() { + delegate.remove(keyReference, this); + } + + @Override public boolean equals(Object obj) { + return referenceEquals(this, obj); + } + } + + protected interface Strategy { + public Object execute(ReferenceMap map, Object keyReference, + Object valueReference); + } + + protected Strategy putStrategy() { + return PutStrategy.PUT; + } + + protected Strategy putIfAbsentStrategy() { + return PutStrategy.PUT_IF_ABSENT; + } + + protected Strategy replaceStrategy() { + return PutStrategy.REPLACE; + } + + private enum PutStrategy implements Strategy { + PUT { + public Object execute(ReferenceMap map, Object keyReference, + Object valueReference) { + return map.delegate.put(keyReference, valueReference); + } + }, + + REPLACE { + public Object execute(ReferenceMap map, Object keyReference, + Object valueReference) { + return map.delegate.replace(keyReference, valueReference); + } + }, + + PUT_IF_ABSENT { + public Object execute(ReferenceMap map, Object keyReference, + Object valueReference) { + return map.delegate.putIfAbsent(keyReference, valueReference); + } + }; + }; + + private static PutStrategy defaultPutStrategy; + + protected PutStrategy getPutStrategy() { + return defaultPutStrategy; + } + + + class Entry implements Map.Entry { + + K key; + V value; + + public Entry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return this.key; + } + + public V getValue() { + return this.value; + } + + public V setValue(V value) { + return put(key, value); + } + + @Override + public int hashCode() { + return key.hashCode() * 31 + value.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ReferenceMap.Entry)) { + return false; + } + + Entry entry = (Entry) o; + return key.equals(entry.key) && value.equals(entry.value); + } + + @Override + public String toString() { + return key + "=" + value; + } + } + + static void ensureNotNull(Object o) { + if (o == null) { + throw new NullPointerException(); + } + } + + static void ensureNotNull(Object... array) { + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + throw new NullPointerException("Argument #" + i + " is null."); + } + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(size()); + for (Map.Entry entry : delegate.entrySet()) { + Object key = dereferenceKey(entry.getKey()); + Object value = dereferenceValue(entry.getValue()); + + // don't persist gc'ed entries. + if (key != null && value != null) { + out.writeObject(key); + out.writeObject(value); + } + } + out.writeObject(null); + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + int size = in.readInt(); + this.delegate = new ConcurrentHashMap(size); + while (true) { + K key = (K) in.readObject(); + if (key == null) { + break; + } + V value = (V) in.readObject(); + put(key, value); + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java new file mode 100644 index 000000000..a223a00ee --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +/** + * Reference type. Used to specify what type of reference to keep to a + * referent. + * + * @see java.lang.ref.Reference + * @author crazybob@google.com (Bob Lee) + */ +public enum ReferenceType { + + /** + * Prevents referent from being reclaimed by the garbage collector. + */ + STRONG, + + /** + * Referent reclaimed in an LRU fashion when the VM runs low on memory and + * no strong references exist. + * + * @see java.lang.ref.SoftReference + */ + SOFT, + + /** + * Referent reclaimed when no strong or soft references exist. + * + * @see java.lang.ref.WeakReference + */ + WEAK, + + /** + * Similar to weak references except the garbage collector doesn't actually + * reclaim the referent. More flexible alternative to finalization. + * + * @see java.lang.ref.PhantomReference + */ + PHANTOM; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java new file mode 100644 index 000000000..a15291ae2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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.inject.util; + +/** + * String utilities. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Strings { + + /** + * Returns a string that is equivalent to the specified string with its + * first character converted to uppercase as by {@link String#toUpperCase}. + * The returned string will have the same value as the specified string if + * its first character is non-alphabetic, if its first character is already + * uppercase, or if the specified string is of length 0. + * + *

For example: + *

+   *    capitalize("foo bar").equals("Foo bar");
+   *    capitalize("2b or not 2b").equals("2b or not 2b")
+   *    capitalize("Foo bar").equals("Foo bar");
+   *    capitalize("").equals("");
+   * 
+ * + * @param s the string whose first character is to be uppercased + * @return a string equivalent to s with its first character + * converted to uppercase + * @throws NullPointerException if s is null + */ + public static String capitalize(String s) { + if (s.length() == 0) + return s; + char first = s.charAt(0); + char capitalized = Character.toUpperCase(first); + return (first == capitalized) + ? s + : capitalized + s.substring(1); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/package.html new file mode 100644 index 000000000..e3264616f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/inject/util/package.html @@ -0,0 +1 @@ +Guice util classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java new file mode 100644 index 000000000..470d34950 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java @@ -0,0 +1,42 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; + +/** + * Provides default implementations of optional lifecycle methods + */ +public abstract class AbstractInterceptor implements Interceptor { + + /** + * Does nothing + */ + public void init() { + } + + /** + * Does nothing + */ + public void destroy() { + } + + + /** + * Override to handle interception + */ + public abstract String intercept(ActionInvocation invocation) throws Exception; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java new file mode 100644 index 000000000..4132c3a13 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java @@ -0,0 +1,193 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Map; + + +/** + * + * + * The aim of this Interceptor is to alias a named parameter to a different named parameter. By acting as the glue + * between actions sharing similiar parameters (but with different names), it can help greatly with action chaining. + * + *

Action's alias expressions should be in the form of #{ "name1" : "alias1", "name2" : "alias2" }. + * This means that assuming an action (or something else in the stack) has a value for the expression named name1 and the + * action this interceptor is applied to has a setter named alias1, alias1 will be set with the value from + * name1. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • aliasesKey (optional) - the name of the action parameter to look for the alias map (by default this is + * aliases).
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * This interceptor does not have any known extension points. + * + * + * + *

Example code: + * + *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <!-- The value for the foo parameter will be applied as if it were named bar -->
+ *     <param name="aliases">#{ 'foo' : 'bar' }</param>
+ *
+ *     <interceptor-ref name="alias"/>
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Matthew Payne + */ +public class AliasInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(AliasInterceptor.class); + + private static final String DEFAULT_ALIAS_KEY = "aliases"; + protected String aliasesKey = DEFAULT_ALIAS_KEY; + + protected ValueStackFactory valueStackFactory; + static boolean devMode = false; + + @Inject("devMode") + public static void setDevMode(String mode) { + devMode = "true".equals(mode); + } + + @Inject + public void setValueStackFactory(ValueStackFactory valueStackFactory) { + this.valueStackFactory = valueStackFactory; + } + + /** + * Sets the name of the action parameter to look for the alias map. + *

+ * Default is aliases. + * + * @param aliasesKey the name of the action parameter + */ + public void setAliasesKey(String aliasesKey) { + this.aliasesKey = aliasesKey; + } + + @Override public String intercept(ActionInvocation invocation) throws Exception { + + ActionConfig config = invocation.getProxy().getConfig(); + ActionContext ac = invocation.getInvocationContext(); + Object action = invocation.getAction(); + + // get the action's parameters + final Map parameters = config.getParams(); + + if (parameters.containsKey(aliasesKey)) { + + String aliasExpression = parameters.get(aliasesKey); + ValueStack stack = ac.getValueStack(); + Object obj = stack.findValue(aliasExpression); + + if (obj != null && obj instanceof Map) { + //get secure stack + ValueStack newStack = valueStackFactory.createValueStack(stack); + boolean clearableStack = newStack instanceof ClearableValueStack; + if (clearableStack) { + //if the stack's context can be cleared, do that to prevent OGNL + //from having access to objects in the stack, see XW-641 + ((ClearableValueStack)newStack).clearContextValues(); + Map context = newStack.getContext(); + ReflectionContextState.setCreatingNullObjects(context, true); + ReflectionContextState.setDenyMethodExecution(context, true); + ReflectionContextState.setReportingConversionErrors(context, true); + + //keep locale from original context + context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE)); + } + + // override + Map aliases = (Map) obj; + for (Object o : aliases.entrySet()) { + Map.Entry entry = (Map.Entry) o; + String name = entry.getKey().toString(); + String alias = (String) entry.getValue(); + Object value = stack.findValue(name); + if (null == value) { + // workaround + Map contextParameters = ActionContext.getContext().getParameters(); + + if (null != contextParameters) { + value = contextParameters.get(name); + } + } + if (null != value) { + try { + newStack.setValue(alias, value); + } catch (RuntimeException e) { + if (devMode) { + String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{ + "Unexpected Exception caught setting '" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage() + }); + LOG.error(developerNotification); + if (action instanceof ValidationAware) { + ((ValidationAware) action).addActionMessage(developerNotification); + } + } + } + } + } + + if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null)) + stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS)); + } else { + LOG.debug("invalid alias expression:" + aliasesKey); + } + } + + return invocation.invoke(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java new file mode 100644 index 000000000..50633089f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java @@ -0,0 +1,176 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.Unchainable; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + +import java.util.*; + + +/** + * + * + * An interceptor that copies all the properties of every object in the value stack to the currently executing object, + * except for any object that implements {@link Unchainable}. A collection of optional includes and + * excludes may be provided to control how and which parameters are copied. Only includes or excludes may be + * specified. Specifying both results in undefined behavior. See the javadocs for {@link ReflectionProvider#copy(Object, Object, + * java.util.Map, java.util.Collection, java.util.Collection)} for more information. + * + *

+ * Note: It is important to remember that this interceptor does nothing if there are no objects already on the stack. + *
This means two things: + *
One, you can safely apply it to all your actions without any worry of adverse affects. + *
Two, it is up to you to ensure an object exists in the stack prior to invoking this action. The most typical way this is done + * is through the use of the chain result type, which combines with this interceptor to make up the action + * chaining feature. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • excludes (optional) - the list of parameter names to exclude from copying (all others will be included).
  • + * + *
  • includes (optional) - the list of parameter names to include when copying (all others will be excluded).
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * There are no known extension points to this interceptor. + * + * + * + *

Example code: + * + *

+ * 
+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success" type="chain">otherAction</result>
+ * </action>
+ *
+ * <action name="otherAction" class="com.examples.OtherAction">
+ *     <interceptor-ref name="chain"/>
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * 
+ * + * @see com.opensymphony.xwork2.ActionChainResult + * @author mrdon + * @author tm_jee ( tm_jee(at)yahoo.co.uk ) + */ +public class ChainingInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(ChainingInterceptor.class); + + protected Collection excludes; + protected Collection includes; + + protected ReflectionProvider reflectionProvider; + + @Inject + public void setReflectionProvider(ReflectionProvider prov) { + this.reflectionProvider = prov; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + ValueStack stack = invocation.getStack(); + CompoundRoot root = stack.getRoot(); + + if (root.size() > 1) { + List list = new ArrayList(root); + list.remove(0); + Collections.reverse(list); + + Map ctxMap = invocation.getInvocationContext().getContextMap(); + Iterator iterator = list.iterator(); + int index = 1; // starts with 1, 0 has been removed + while (iterator.hasNext()) { + index = index + 1; + Object o = iterator.next(); + if (o != null) { + if (!(o instanceof Unchainable)) { + reflectionProvider.copy(o, invocation.getAction(), ctxMap, excludes, includes); + } + } + else { + LOG.warn("compound root element at index "+index+" is null"); + } + } + } + + return invocation.invoke(); + } + + /** + * Gets list of parameter names to exclude + * + * @return the exclude list + */ + public Collection getExcludes() { + return excludes; + } + + /** + * Sets the list of parameter names to exclude from copying (all others will be included). + * + * @param excludes the excludes list + */ + public void setExcludes(Collection excludes) { + this.excludes = excludes; + } + + /** + * Gets list of parameter names to include + * + * @return the include list + */ + public Collection getIncludes() { + return includes; + } + + /** + * Sets the list of parameter names to include when copying (all others will be excluded). + * + * @param includes the includes list + */ + public void setIncludes(Collection includes) { + this.includes = includes; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java new file mode 100644 index 000000000..65793c289 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java @@ -0,0 +1,139 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.HashMap; +import java.util.Map; + + +/** + * + * ConversionErrorInterceptor adds conversion errors from the ActionContext to the Action's field errors. + * + *

+ * This interceptor adds any error found in the {@link ActionContext}'s conversionErrors map as a field error (provided + * that the action implements {@link ValidationAware}). In addition, any field that contains a validation error has its + * original value saved such that any subsequent requests for that value return the original value rather than the value + * in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to + * display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to + * the user). + * + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • None
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * Because this interceptor is not web-specific, it abstracts the logic for whether an error should be added. This + * allows for web-specific interceptors to use more complex logic in the {@link #shouldAddError} method for when a value + * has a conversion error but is null or empty or otherwise indicates that the value was never actually entered by the + * user. + * + * + * + *

Example code: + * + *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="conversionError"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Jason Carreira + */ +public class ConversionErrorInterceptor extends AbstractInterceptor { + + public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override"; + + protected Object getOverrideExpr(ActionInvocation invocation, Object value) { + return "'" + value + "'"; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + + ActionContext invocationContext = invocation.getInvocationContext(); + Map conversionErrors = invocationContext.getConversionErrors(); + ValueStack stack = invocationContext.getValueStack(); + + HashMap fakie = null; + + for (Map.Entry entry : conversionErrors.entrySet()) { + String propertyName = entry.getKey(); + Object value = entry.getValue(); + + if (shouldAddError(propertyName, value)) { + String message = XWorkConverter.getConversionErrorMessage(propertyName, stack); + + Object action = invocation.getAction(); + if (action instanceof ValidationAware) { + ValidationAware va = (ValidationAware) action; + va.addFieldError(propertyName, message); + } + + if (fakie == null) { + fakie = new HashMap(); + } + + fakie.put(propertyName, getOverrideExpr(invocation, value)); + } + } + + if (fakie != null) { + // if there were some errors, put the original (fake) values in place right before the result + stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie); + invocation.addPreResultListener(new PreResultListener() { + public void beforeResult(ActionInvocation invocation, String resultCode) { + Map fakie = (Map) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE); + + if (fakie != null) { + invocation.getStack().setExprOverrides(fakie); + } + } + }); + } + return invocation.invoke(); + } + + protected boolean shouldAddError(String propertyName, Object value) { + return true; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java new file mode 100644 index 000000000..1cdf20f4f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.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.interceptor; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.interceptor.annotations.InputConfig; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.lang.reflect.Method; + +/** + * + *

+ * An interceptor that makes sure there are not validation errors before allowing the interceptor chain to continue. + * This interceptor does not perform any validation. + *

+ *

This interceptor does nothing if the name of the method being invoked is specified in the excludeMethods + * parameter. excludeMethods accepts a comma-delimited list of method names. For example, requests to + * foo!input.action and foo!back.action will be skipped by this interceptor if you set the + * excludeMethods parameter to "input, back". + *

+ * Note: As this method extends off MethodFilterInterceptor, it is capable of + * deciding if it is applicable only to selective methods in the action class. This is done by adding param tags + * for the interceptor element, naming either a list of excluded method names and/or a list of included method + * names, whereby includeMethods overrides excludedMethods. A single * sign is interpreted as wildcard matching + * all methods for both parameters. + * See {@link MethodFilterInterceptor} for more info. + *

+ * + *

+ *

Interceptor parameters: + *

+ * + *

+ *

    + *

    + *

  • inputResultName - Default to "input". Determine the result name to be returned when + * an action / field error is found.
  • + *

    + *

+ *

+ * + *

+ *

Extending the interceptor: + *

+ *

+ *

+ * + *

+ * There are no known extension points for this interceptor. + *

+ * + *

+ *

Example code: + *

+ *

+ * 
+ * 

+ * <action name="someAction" class="com.examples.SomeAction"> + * <interceptor-ref name="params"/> + * <interceptor-ref name="validation"/> + * <interceptor-ref name="workflow"/> + * <result name="success">good_result.ftl</result> + * </action> + *

+ * <-- In this case myMethod as well as mySecondMethod of the action class + * will not pass through the workflow process --> + * <action name="someAction" class="com.examples.SomeAction"> + * <interceptor-ref name="params"/> + * <interceptor-ref name="validation"/> + * <interceptor-ref name="workflow"> + * <param name="excludeMethods">myMethod,mySecondMethod</param> + * </interceptor-ref name="workflow"> + * <result name="success">good_result.ftl</result> + * </action> + *

+ * <-- In this case, the result named "error" will be used when + * an action / field error is found --> + * <-- The Interceptor will only be applied for myWorkflowMethod method of action + * classes, since this is the only included method while any others are excluded --> + * <action name="someAction" class="com.examples.SomeAction"> + * <interceptor-ref name="params"/> + * <interceptor-ref name="validation"/> + * <interceptor-ref name="workflow"> + * <param name="inputResultName">error</param> + * <param name="excludeMethods">*</param> + * <param name="includeMethods">myWorkflowMethod</param> + * </interceptor-ref> + * <result name="success">good_result.ftl</result> + * </action> + *

+ * + *

+ * + * @author Jason Carreira + * @author Rainer Hermanns + * @author Alexandru Popescu + * @author Philip Luppens + * @author tm_jee + */ +public class DefaultWorkflowInterceptor extends MethodFilterInterceptor { + + private static final long serialVersionUID = 7563014655616490865L; + + private static final Logger LOG = LoggerFactory.getLogger(DefaultWorkflowInterceptor.class); + + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + private String inputResultName = Action.INPUT; + + /** + * Set the inputResultName (result name to be returned when + * a action / field error is found registered). Default to {@link Action#INPUT} + * + * @param inputResultName what result name to use when there was validation error(s). + */ + public void setInputResultName(String inputResultName) { + this.inputResultName = inputResultName; + } + + /** + * Intercept {@link ActionInvocation} and returns a inputResultName + * when action / field errors is found registered. + * + * @return String result name + */ + @Override + protected String doIntercept(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + + if (action instanceof ValidationAware) { + ValidationAware validationAwareAction = (ValidationAware) action; + + if (validationAwareAction.hasErrors()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Errors on action " + validationAwareAction + ", returning result name 'input'"); + } + + String resultName = inputResultName; + + if (action instanceof ValidationWorkflowAware) { + resultName = ((ValidationWorkflowAware) action).getInputResultName(); + } + + InputConfig annotation = action.getClass().getMethod(invocation.getProxy().getMethod(), EMPTY_CLASS_ARRAY).getAnnotation(InputConfig.class); + if (annotation != null) { + if (!annotation.methodName().equals("")) { + Method method = action.getClass().getMethod(annotation.methodName()); + resultName = (String) method.invoke(action); + } else { + resultName = annotation.resultName(); + } + } + + + return resultName; + } + } + + return invocation.invoke(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java new file mode 100644 index 000000000..90382b5c9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java @@ -0,0 +1,84 @@ +/* + * 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.interceptor; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Serializable; + +/** + * + * + * A simple wrapper around an exception, providing an easy way to print out the stack trace of the exception as well as + * a way to get a handle on the exception itself. + * + * + * + * @author Matthew E. Porter (matthew dot porter at metissian dot com) + */ +public class ExceptionHolder implements Serializable { + + private Exception exception; + + /** + * Holds the given exception + * + * @param exception the exception to hold. + */ + public ExceptionHolder(Exception exception) { + this.exception = exception; + } + + /** + * Gets the holded exception + * + * @return the holded exception + */ + public Exception getException() { + return this.exception; + } + + /** + * Gets the holded exception stacktrace using {@link Exception#printStackTrace()}. + * + * @return stacktrace + */ + public String getExceptionStack() { + String exceptionStack = null; + + if (getException() != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + try { + getException().printStackTrace(pw); + exceptionStack = sw.toString(); + } + finally { + try { + sw.close(); + pw.close(); + } catch (IOException e) { + // ignore + } + } + } + + return exceptionStack; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java new file mode 100644 index 000000000..1627f26a4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java @@ -0,0 +1,304 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.List; + +/** + * + * + * This interceptor forms the core functionality of the exception handling feature. Exception handling allows you to map + * an exception to a result code, just as if the action returned a result code instead of throwing an unexpected + * exception. When an exception is encountered, it is wrapped with an {@link ExceptionHolder} and pushed on the stack, + * providing easy access to the exception from within your result. + * + * Note: While you can configure exception mapping in your configuration file at any point, the configuration + * will not have any effect if this interceptor is not in the interceptor stack for your actions. It is recommended that + * you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any + * exception, even those caused by other interceptors. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • logEnabled (optional) - Should exceptions also be logged? (boolean true|false)
  • + * + *
  • logLevel (optional) - what log level should we use (trace, debug, info, warn, error, fatal)? - defaut is debug
  • + * + *
  • logCategory (optional) - If provided we would use this category (eg. com.mycompany.app). + * Default is to use com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.
  • + * + *
+ * + * The parameters above enables us to log all thrown exceptions with stacktace in our own logfile, + * and present a friendly webpage (with no stacktrace) to the end user. + * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * If you want to add custom handling for publishing the Exception, you may override + * {@link #publishException(com.opensymphony.xwork2.ActionInvocation, ExceptionHolder)}. The default implementation + * pushes the given ExceptionHolder on value stack. A custom implementation could add additional logging etc. + * + * + * + *

Example code: + * + *

+ * 
+ * <xwork>
+ *     <package name="default" extends="xwork-default">
+ *         <global-results>
+ *             <result name="error" type="freemarker">error.ftl</result>
+ *         </global-results>
+ *
+ *         <global-exception-mappings>
+ *             <exception-mapping exception="java.lang.Exception" result="error"/>
+ *         </global-exception-mappings>
+ *
+ *         <action name="test">
+ *             <interceptor-ref name="exception"/>
+ *             <interceptor-ref name="basicStack"/>
+ *             <exception-mapping exception="com.acme.CustomException" result="custom_error"/>
+ *             <result name="custom_error">custom_error.ftl</result>
+ *             <result name="success" type="freemarker">test.ftl</result>
+ *         </action>
+ *     </package>
+ * </xwork>
+ * 
+ * 
+ * + *

+ * This second example will also log the exceptions using our own category + * com.mycompany.app.unhandled at WARN level. + * + *

+ * 
+ * <xwork>
+ *   <package name="something" extends="xwork-default">
+ *      <interceptors>
+ *          <interceptor-stack name="exceptionmappingStack">
+ *              <interceptor-ref name="exception">
+ *                  <param name="logEnabled">true</param>
+ *                  <param name="logCategory">com.mycompany.app.unhandled</param>
+ *                  <param name="logLevel">WARN</param>	        		
+ *              </interceptor-ref>	
+ *              <interceptor-ref name="i18n"/>
+ *              <interceptor-ref name="staticParams"/>
+ *              <interceptor-ref name="params"/>
+ *              <interceptor-ref name="validation">
+ *                  <param name="excludeMethods">input,back,cancel,browse</param>
+ *              </interceptor-ref>
+ *          </interceptor-stack>
+ *      </interceptors>
+ *
+ *      <default-interceptor-ref name="exceptionmappingStack"/>
+ *    
+ *      <global-results>
+ *           <result name="unhandledException">/unhandled-exception.jsp</result>
+ *      </global-results>
+ *
+ *      <global-exception-mappings>
+ *           <exception-mapping exception="java.lang.Exception" result="unhandledException"/>
+ *      </global-exception-mappings>
+ *        
+ *      <action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction">
+ *          <exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
+ *                             result="damm"/>
+ *          <result name="input">index.jsp</result>
+ *          <result name="success">success.jsp</result>            
+ *          <result name="damm">damm.jsp</result>
+ *      </action>
+ *
+ *   </package>
+ * </xwork>
+ * 
+ * 
+ * + * @author Matthew E. Porter (matthew dot porter at metissian dot com) + * @author Claus Ibsen + */ +public class ExceptionMappingInterceptor extends AbstractInterceptor { + + protected static final Logger LOG = LoggerFactory.getLogger(ExceptionMappingInterceptor.class); + + protected Logger categoryLogger; + protected boolean logEnabled = false; + protected String logCategory; + protected String logLevel; + + + public boolean isLogEnabled() { + return logEnabled; + } + + public void setLogEnabled(boolean logEnabled) { + this.logEnabled = logEnabled; + } + + public String getLogCategory() { + return logCategory; + } + + public void setLogCategory(String logCatgory) { + this.logCategory = logCatgory; + } + + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + String result; + + try { + result = invocation.invoke(); + } catch (Exception e) { + if (isLogEnabled()) { + handleLogging(e); + } + List exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings(); + String mappedResult = this.findResultFromExceptions(exceptionMappings, e); + if (mappedResult != null) { + result = mappedResult; + publishException(invocation, new ExceptionHolder(e)); + } else { + throw e; + } + } + + return result; + } + + /** + * Handles the logging of the exception. + * + * @param e the exception to log. + */ + protected void handleLogging(Exception e) { + if (logCategory != null) { + if (categoryLogger == null) { + // init category logger + categoryLogger = LoggerFactory.getLogger(logCategory); + } + doLog(categoryLogger, e); + } else { + doLog(LOG, e); + } + } + + /** + * Performs the actual logging. + * + * @param logger the provided logger to use. + * @param e the exception to log. + */ + protected void doLog(Logger logger, Exception e) { + if (logLevel == null) { + logger.debug(e.getMessage(), e); + return; + } + + if ("trace".equalsIgnoreCase(logLevel)) { + logger.trace(e.getMessage(), e); + } else if ("debug".equalsIgnoreCase(logLevel)) { + logger.debug(e.getMessage(), e); + } else if ("info".equalsIgnoreCase(logLevel)) { + logger.info(e.getMessage(), e); + } else if ("warn".equalsIgnoreCase(logLevel)) { + logger.warn(e.getMessage(), e); + } else if ("error".equalsIgnoreCase(logLevel)) { + logger.error(e.getMessage(), e); + } else if ("fatal".equalsIgnoreCase(logLevel)) { + logger.fatal(e.getMessage(), e); + } else { + throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported"); + } + } + + protected String findResultFromExceptions(List exceptionMappings, Throwable t) { + String result = null; + + // Check for specific exception mappings. + if (exceptionMappings != null) { + int deepest = Integer.MAX_VALUE; + for (Object exceptionMapping : exceptionMappings) { + ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping; + int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t); + if (depth >= 0 && depth < deepest) { + deepest = depth; + result = exceptionMappingConfig.getResult(); + } + } + } + + return result; + } + + /** + * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match. + * Otherwise, returns depth. Lowest depth wins. + * + * @param exceptionMapping the mapping classname + * @param t the cause + * @return the depth, if not found -1 is returned. + */ + public int getDepth(String exceptionMapping, Throwable t) { + return getDepth(exceptionMapping, t.getClass(), 0); + } + + private int getDepth(String exceptionMapping, Class exceptionClass, int depth) { + if (exceptionClass.getName().contains(exceptionMapping)) { + // Found it! + return depth; + } + // If we've gone as far as we can go and haven't found it... + if (exceptionClass.equals(Throwable.class)) { + return -1; + } + return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); + } + + /** + * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack. + * Subclasses may override this to customize publishing. + * + * @param invocation The invocation to publish Exception for. + * @param exceptionHolder The exceptionHolder wrapping the Exception to publish. + */ + protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) { + invocation.getStack().push(exceptionHolder); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java new file mode 100644 index 000000000..e8fb4f121 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java @@ -0,0 +1,211 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Locale; +import java.util.Map; + +/** + * + *

+ * An interceptor that handles setting the locale specified in a session as the locale for the current action request. + * In addition, this interceptor will look for a specific HTTP request parameter and set the locale to whatever value is + * provided. This means that this interceptor can be used to allow for your application to dynamically change the locale + * for the user's session or, alternatively, only for the current request (since XWork 2.1.3). + * This is very useful for applications that require multi-lingual support and want the user to + * be able to set his or her language preference at any point. The locale parameter is removed during the execution of + * this interceptor, ensuring that properties aren't set on an action (such as request_locale) that have no typical + * corresponding setter in your action. + *

+ *

For example, using the default parameter name, a request to foo.action?request_locale=en_US, then the + * locale for US English is saved in the user's session and will be used for all future requests. + *

+ * + *

+ *

Interceptor parameters: + *

+ * + *

+ *

    + *

    + *

  • parameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to and save + * in the session. By default this is request_locale
  • + *

    + *

  • requestOnlyParameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to + * for the current request only, without saving it in the session. By default this is request_only_locale
  • + *

    + *

  • attributeName (optional) - the name of the session key to store the selected locale. By default this is + * WW_TRANS_I18N_LOCALE
  • + *

    + *

+ *

+ * + *

+ *

Extending the interceptor: + *

+ *

+ *

+ * + *

+ * There are no known extensions points for this interceptor. + *

+ * + *

+ *

Example code: + *

+ *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="i18n"/>
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Aleksei Gopachenko + */ +public class I18nInterceptor extends AbstractInterceptor { + protected static final Logger LOG = LoggerFactory.getLogger(I18nInterceptor.class); + + public static final String DEFAULT_SESSION_ATTRIBUTE = "WW_TRANS_I18N_LOCALE"; + public static final String DEFAULT_PARAMETER = "request_locale"; + public static final String DEFAULT_REQUESTONLY_PARAMETER = "request_only_locale"; + + protected String parameterName = DEFAULT_PARAMETER; + protected String requestOnlyParameterName = DEFAULT_REQUESTONLY_PARAMETER; + protected String attributeName = DEFAULT_SESSION_ATTRIBUTE; + + public I18nInterceptor() { + if (LOG.isDebugEnabled()) { + LOG.debug("new I18nInterceptor()"); + } + } + + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } + + public void setRequestOnlyParameterName(String requestOnlyParameterName) { + this.requestOnlyParameterName = requestOnlyParameterName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + if (LOG.isDebugEnabled()) { + LOG.debug("intercept '" + + invocation.getProxy().getNamespace() + "/" + + invocation.getProxy().getActionName() + "' { "); + } + //get requested locale + Map params = invocation.getInvocationContext().getParameters(); + + boolean storeInSession = true; + Object requested_locale = findLocaleParameter(params, parameterName); + if (requested_locale == null) { + requested_locale = findLocaleParameter(params, requestOnlyParameterName); + if (requested_locale != null) { + storeInSession = false; + } + } + + //save it in session + Map session = invocation.getInvocationContext().getSession(); + + Locale locale = null; + if (requested_locale != null) { + locale = (requested_locale instanceof Locale) ? + (Locale) requested_locale : LocalizedTextUtil.localeFromString(requested_locale.toString(), null); + if (locale != null && LOG.isDebugEnabled()) { + LOG.debug("applied request locale=" + locale); + } + } + if (session != null) { + synchronized (session) { + if (locale == null) { + storeInSession = false; + // check session for saved locale + Object sessionLocale = session.get(attributeName); + if (sessionLocale != null && sessionLocale instanceof Locale) { + locale = (Locale) sessionLocale; + if (LOG.isDebugEnabled()) { + LOG.debug("applied session locale=" + locale); + } + } else { + // no overriding locale definition found, stay with current invocation (=browser) locale + locale = invocation.getInvocationContext().getLocale(); + if (locale != null && LOG.isDebugEnabled()) { + LOG.debug("applied invocation context locale=" + locale); + } + } + } + if (storeInSession) { + session.put(attributeName, locale); + } + } + } + saveLocale(invocation, locale); + + if (LOG.isDebugEnabled()) { + LOG.debug("before Locale=" + invocation.getStack().findValue("locale")); + } + + final String result = invocation.invoke(); + if (LOG.isDebugEnabled()) { + LOG.debug("after Locale=" + invocation.getStack().findValue("locale")); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("intercept } "); + } + + return result; + } + + private Object findLocaleParameter(Map params, String parameterName) { + Object requested_locale = params.remove(parameterName); + if (requested_locale != null && requested_locale.getClass().isArray() + && ((Object[]) requested_locale).length == 1) { + requested_locale = ((Object[]) requested_locale)[0]; + + if (LOG.isDebugEnabled()) { + LOG.debug("requested_locale=" + requested_locale); + } + } + return requested_locale; + } + + /** + * Save the given locale to the ActionInvocation. + * + * @param invocation The ActionInvocation. + * @param locale The locale to save. + */ + protected void saveLocale(ActionInvocation invocation, Locale locale) { + invocation.getInvocationContext().setLocale(locale); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java new file mode 100644 index 000000000..426623648 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java @@ -0,0 +1,213 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; + +import java.io.Serializable; + + +/** + * + *

+ * An interceptor is a stateless class that follows the interceptor pattern, as + * found in {@link javax.servlet.Filter} and in AOP languages. + *

+ *

+ *

+ * Interceptors are objects that dynamically intercept Action invocations. + * They provide the developer with the opportunity to define code that can be executed + * before and/or after the execution of an action. They also have the ability + * to prevent an action from executing. Interceptors provide developers a way to + * encapulate common functionality in a re-usable form that can be applied to + * one or more Actions. + *

+ *

+ *

+ * Interceptors must be stateless and not assume that a new instance will be created for each request or Action. + * Interceptors may choose to either short-circuit the {@link ActionInvocation} execution and return a return code + * (such as {@link com.opensymphony.xwork2.Action#SUCCESS}), or it may choose to do some processing before + * and/or after delegating the rest of the procesing using {@link ActionInvocation#invoke()}. + *

+ * + *

+ *

+ *

+ * + *

+ * Interceptor's parameter could be overriden through the following ways :- + *

+ *

+ *

+ * Method 1: + *

+ * <action name="myAction" class="myActionClass">
+ *     <interceptor-ref name="exception"/>
+ *     <interceptor-ref name="alias"/>
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="servletConfig"/>
+ *     <interceptor-ref name="prepare"/>
+ *     <interceptor-ref name="i18n"/>
+ *     <interceptor-ref name="chain"/>
+ *     <interceptor-ref name="modelDriven"/>
+ *     <interceptor-ref name="fileUpload"/>
+ *     <interceptor-ref name="staticParams"/>
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="conversionError"/>
+ *     <interceptor-ref name="validation">
+ *     <param name="excludeMethods">myValidationExcudeMethod</param>
+ *     </interceptor-ref>
+ *     <interceptor-ref name="workflow">
+ *     <param name="excludeMethods">myWorkflowExcludeMethod</param>
+ *     </interceptor-ref>
+ * </action>
+ * 
+ *

+ * Method 2: + *

+ * <action name="myAction" class="myActionClass">
+ *   <interceptor-ref name="defaultStack">
+ *     <param name="validation.excludeMethods">myValidationExcludeMethod</param>
+ *     <param name="workflow.excludeMethods">myWorkflowExcludeMethod</param>
+ *   </interceptor-ref>
+ * </action>
+ * 
+ *

+ *

+ *

+ * In the first method, the whole default stack is copied and the parameter then + * changed accordingly. + *

+ *

+ *

+ * In the second method, the 'interceptor-ref' refer to an existing + * interceptor-stack, namely defaultStack in this example, and override the validator + * and workflow interceptor excludeMethods typically in this case. Note that in the + * 'param' tag, the name attribute contains a dot (.) the word before the dot(.) + * specifies the interceptor name whose parameter is to be overridden and the word after + * the dot (.) specifies the parameter itself. Essetially it is as follows :- + *

+ *

+ *    <interceptor-name>.<parameter-name>
+ * 
+ *

+ * Note also that in this case the 'interceptor-ref' name attribute + * is used to indicate an interceptor stack which makes sense as if it is referring + * to the interceptor itself it would be just using Method 1 describe above. + *

+ * + *

+ *

+ * Nested Interceptor param overriding + *

+ * + *

+ * Interceptor stack parameter overriding could be nested into as many level as possible, though it would + * be advisable not to nest it too deep as to avoid confusion, For example, + *

+ * <interceptor name="interceptor1" class="foo.bar.Interceptor1" />
+ * <interceptor name="interceptor2" class="foo.bar.Interceptor2" />
+ * <interceptor name="interceptor3" class="foo.bar.Interceptor3" />
+ * <interceptor name="interceptor4" class="foo.bar.Interceptor4" />
+ * <interceptor-stack name="stack1">
+ *     <interceptor-ref name="interceptor1" />
+ * </interceptor-stack>
+ * <interceptor-stack name="stack2">
+ *     <interceptor-ref name="intercetor2" />
+ *     <interceptor-ref name="stack1" />
+ * </interceptor-stack>
+ * <interceptor-stack name="stack3">
+ *     <interceptor-ref name="interceptor3" />
+ *     <interceptor-ref name="stack2" />
+ * </interceptor-stack>
+ * <interceptor-stack name="stack4">
+ *     <interceptor-ref name="interceptor4" />
+ *     <interceptor-ref name="stack3" />
+ *  </interceptor-stack>
+ * 
+ * Assuming the interceptor has the following properties + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Interceptorproperty
Interceptor1param1
Interceptor2param2
Interceptor3param3
Interceptor4param4
+ * We could override them as follows :- + *
+ *    <action ... >
+ *        <!-- to override parameters of interceptor located directly in the stack  -->
+ *        <interceptor-ref name="stack4">
+ *           <param name="interceptor4.param4"> ... </param>
+ *        </interceptor-ref>
+ *    </action>
+ * 

+ * <action ... > + * <!-- to override parameters of interceptor located under nested stack --> + * <interceptor-ref name="stack4"> + * <param name="stack3.interceptor3.param3"> ... </param> + * <param name="stack3.stack2.interceptor2.param2"> ... </param> + * <param name="stack3.stack2.stack1.interceptor1.param1"> ... </param> + * </interceptor-ref> + * </action> + *

+ *

+ * + * + * @author Jason Carreira + * @author tmjee + * @version $Date$ $Id$ + */ +public interface Interceptor extends Serializable { + + /** + * Called to let an interceptor clean up any resources it has allocated. + */ + void destroy(); + + /** + * Called after an interceptor is created, but before any requests are processed using + * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving + * the Interceptor a chance to initialize any needed resources. + */ + void init(); + + /** + * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the + * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code. + * + * @param invocation the action invocation + * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself. + * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}. + */ + String intercept(ActionInvocation invocation) throws Exception; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java new file mode 100644 index 000000000..d71956990 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java @@ -0,0 +1,86 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + + +/** + * + * This interceptor logs the start and end of the execution an action (in English-only, not internationalized). + *
+ * Note:: This interceptor will log at INFO level. + *

+ * + * + * + * There are no parameters for this interceptor. + * + * + * + * There are no obvious extensions to the existing interceptor. + * + * + *

+ * 
+ * <!-- prints out a message before and after the immediate action execution -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="completeStack"/>
+ *     <interceptor-ref name="logger"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <!-- prints out a message before any more interceptors continue and after they have finished -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="logger"/>
+ *     <interceptor-ref name="completeStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Jason Carreira + */ +public class LoggingInterceptor extends AbstractInterceptor { + private static final Logger LOG = LoggerFactory.getLogger(LoggingInterceptor.class); + private static final String FINISH_MESSAGE = "Finishing execution stack for action "; + private static final String START_MESSAGE = "Starting execution stack for action "; + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + logMessage(invocation, START_MESSAGE); + String result = invocation.invoke(); + logMessage(invocation, FINISH_MESSAGE); + return result; + } + + private void logMessage(ActionInvocation invocation, String baseMessage) { + if (LOG.isInfoEnabled()) { + StringBuilder message = new StringBuilder(baseMessage); + String namespace = invocation.getProxy().getNamespace(); + + if ((namespace != null) && (namespace.trim().length() > 0)) { + message.append(namespace).append("/"); + } + + message.append(invocation.getProxy().getActionName()); + LOG.info(message.toString()); + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java new file mode 100644 index 000000000..fd44df86a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java @@ -0,0 +1,124 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Collections; +import java.util.Set; + + +/** + * + * + * MethodFilterInterceptor is an abstract Interceptor used as + * a base class for interceptors that will filter execution based on method + * names according to specified included/excluded method lists. + * + *

+ * + * Settable parameters are as follows: + * + *

    + *
  • excludeMethods - method names to be excluded from interceptor processing
  • + *
  • includeMethods - method names to be included in interceptor processing
  • + *
+ * + *

+ * + * NOTE: If method name are available in both includeMethods and + * excludeMethods, it will be considered as an included method: + * includeMethods takes precedence over excludeMethods. + * + *

+ * + * Interceptors that extends this capability include: + * + *

    + *
  • TokenInterceptor
  • + *
  • TokenSessionStoreInterceptor
  • + *
  • DefaultWorkflowInterceptor
  • + *
  • ValidationInterceptor
  • + *
+ * + * + * + * @author Alexandru Popescu + * @author Rainer Hermanns + * + * @see org.apache.struts2.interceptor.TokenInterceptor + * @see org.apache.struts2.interceptor.TokenSessionStoreInterceptor + * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor + * @see com.opensymphony.xwork2.validator.ValidationInterceptor + * + * @version $Date$ $Id$ + */ +public abstract class MethodFilterInterceptor extends AbstractInterceptor { + protected transient Logger log = LoggerFactory.getLogger(getClass()); + + protected Set excludeMethods = Collections.emptySet(); + protected Set includeMethods = Collections.emptySet(); + + public void setExcludeMethods(String excludeMethods) { + this.excludeMethods = TextParseUtil.commaDelimitedStringToSet(excludeMethods); + } + + public Set getExcludeMethodsSet() { + return excludeMethods; + } + + public void setIncludeMethods(String includeMethods) { + this.includeMethods = TextParseUtil.commaDelimitedStringToSet(includeMethods); + } + + public Set getIncludeMethodsSet() { + return includeMethods; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + if (applyInterceptor(invocation)) { + return doIntercept(invocation); + } + return invocation.invoke(); + } + + protected boolean applyInterceptor(ActionInvocation invocation) { + String method = invocation.getProxy().getMethod(); + // ValidationInterceptor + boolean applyMethod = MethodFilterInterceptorUtil.applyMethod(excludeMethods, includeMethods, method); + if (log.isDebugEnabled()) { + if (!applyMethod) { + log.debug("Skipping Interceptor... Method [" + method + "] found in exclude list."); + } + } + return applyMethod; + } + + /** + * Subclasses must override to implement the interceptor logic. + * + * @param invocation the action invocation + * @return the result of invocation + * @throws Exception + */ + protected abstract String doIntercept(ActionInvocation invocation) throws Exception; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java new file mode 100644 index 000000000..c9d0c927b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java @@ -0,0 +1,145 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.WildcardHelper; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; + +/** + * Utility class contains common methods used by + * {@link com.opensymphony.xwork2.interceptor.MethodFilterInterceptor}. + * + * @author tm_jee + */ +public class MethodFilterInterceptorUtil { + + /** + * Static method to decide if the specified method should be + * apply (not filtered) depending on the set of excludeMethods and + * includeMethods. + * + *
    + *
  • + * includeMethods takes precedence over excludeMethods + *
  • + *
+ * Note: Supports wildcard listings in includeMethods/excludeMethods + * + * @param excludeMethods list of methods to exclude. + * @param includeMethods list of methods to include. + * @param method the specified method to check + * @return true if the method should be applied. + */ + public static boolean applyMethod(Set excludeMethods, Set includeMethods, String method) { + + // quick check to see if any actual pattern matching is needed + boolean needsPatternMatch = false; + Iterator quickIter = includeMethods.iterator(); + for (String incMeth : includeMethods) { + if (!"*".equals(incMeth) && incMeth.contains("*")) { + needsPatternMatch = true; + } + } + + for (String incMeth : excludeMethods) { + if (!"*".equals(incMeth) && incMeth.contains("*")) { + needsPatternMatch = true; + } + } + + // this section will try to honor the original logic, while + // still allowing for wildcards later + if (!needsPatternMatch && (includeMethods.contains("*") || includeMethods.size() == 0) ) { + if (excludeMethods != null + && excludeMethods.contains(method) + && !includeMethods.contains(method) ) { + return false; + } + } + + // test the methods using pattern matching + WildcardHelper wildcard = new WildcardHelper(); + String methodCopy ; + if (method == null ) { // no method specified + methodCopy = ""; + } + else { + methodCopy = new String(method); + } + for (String pattern : includeMethods) { + if (pattern.contains("*")) { + int[] compiledPattern = wildcard.compilePattern(pattern); + HashMap matchedPatterns = new HashMap(); + boolean matches = wildcard.match(matchedPatterns, methodCopy, compiledPattern); + if (matches) { + return true; // run it, includeMethods takes precedence + } + } + else { + if (pattern.equals(methodCopy)) { + return true; // run it, includeMethods takes precedence + } + } + } + if (excludeMethods.contains("*") ) { + return false; + } + + // CHECK ME: Previous implementation used include method + for ( String pattern : excludeMethods) { + if (pattern.contains("*")) { + int[] compiledPattern = wildcard.compilePattern(pattern); + HashMap matchedPatterns = new HashMap(); + boolean matches = wildcard.match(matchedPatterns, methodCopy, compiledPattern); + if (matches) { + // if found, and wasn't included earlier, don't run it + return false; + } + } + else { + if (pattern.equals(methodCopy)) { + // if found, and wasn't included earlier, don't run it + return false; + } + } + } + + + // default fall-back from before changes + return includeMethods.size() == 0 || includeMethods.contains(method) || includeMethods.contains("*"); + } + + /** + * Same as {@link #applyMethod(Set, Set, String)}, except that excludeMethods + * and includeMethods are supplied as comma separated string. + * + * @param excludeMethods comma seperated string of methods to exclude. + * @param includeMethods comma seperated string of methods to include. + * @param method the specified method to check + * @return true if the method should be applied. + */ + public static boolean applyMethod(String excludeMethods, String includeMethods, String method) { + Set includeMethodsSet = TextParseUtil.commaDelimitedStringToSet(includeMethods == null? "" : includeMethods); + Set excludeMethodsSet = TextParseUtil.commaDelimitedStringToSet(excludeMethods == null? "" : excludeMethods); + + return applyMethod(excludeMethodsSet, includeMethodsSet, method); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java new file mode 100644 index 000000000..f145534b5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java @@ -0,0 +1,143 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ModelDriven; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; + + +/** + * + * + * Watches for {@link ModelDriven} actions and adds the action's model on to the value stack. + * + *

Note: The ModelDrivenInterceptor must come before the both {@link StaticParametersInterceptor} and + * {@link ParametersInterceptor} if you want the parameters to be applied to the model. + * + *

Note: The ModelDrivenInterceptor will only push the model into the stack when the + * model is not null, else it will be ignored. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • refreshModelBeforeResult - set to true if you want the model to be refreshed on the value stack after action + * execution and before result execution. The setting is useful if you want to change the model instance during the + * action execution phase, like when loading it from the data layer. This will result in getModel() being called at + * least twice.
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * There are no known extension points to this interceptor. + * + * + * + *

Example code: + * + *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="modelDriven"/>
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ModelDrivenInterceptor extends AbstractInterceptor { + + protected boolean refreshModelBeforeResult = false; + + public void setRefreshModelBeforeResult(boolean val) { + this.refreshModelBeforeResult = val; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + + if (action instanceof ModelDriven) { + ModelDriven modelDriven = (ModelDriven) action; + ValueStack stack = invocation.getStack(); + Object model = modelDriven.getModel(); + if (model != null) { + stack.push(model); + } + if (refreshModelBeforeResult) { + invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); + } + } + return invocation.invoke(); + } + + /** + * Refreshes the model instance on the value stack, if it has changed + */ + protected static class RefreshModelBeforeResult implements PreResultListener { + private Object originalModel = null; + protected ModelDriven action; + + + public RefreshModelBeforeResult(ModelDriven action, Object model) { + this.originalModel = model; + this.action = action; + } + + public void beforeResult(ActionInvocation invocation, String resultCode) { + ValueStack stack = invocation.getStack(); + CompoundRoot root = stack.getRoot(); + + boolean needsRefresh = true; + Object newModel = action.getModel(); + + // Check to see if the new model instance is already on the stack + for (Object item : root) { + if (item.equals(newModel)) { + needsRefresh = false; + } + } + + // Add the new model on the stack + if (needsRefresh) { + + // Clear off the old model instance + if (originalModel != null) { + root.remove(originalModel); + } + if (newModel != null) { + stack.push(newModel); + } + } + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java new file mode 100644 index 000000000..8db8fbce7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java @@ -0,0 +1,32 @@ +/* + * 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.interceptor; + + +/** + * Marker interface to incidate no auto setting of parameters. + *

+ * This marker interface should be implemented by actions that do not want any + * request parameters set on them automatically (by the ParametersInterceptor). + * This may be useful if one is using the action tag and want to supply + * the parameters to the action manually using the param tag. + * It may also be useful if one for security reasons wants to make sure that + * parameters cannot be set by malicious users. + * + * @author Dick Zetterberg (dick@transitor.se) + */ +public interface NoParameters { +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java new file mode 100644 index 000000000..89a6f4796 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java @@ -0,0 +1,247 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.TreeMap; + +/** + * + * + * The Parameter Filter Interceptor blocks parameters from getting + * to the rest of the stack or your action. You can use multiple + * parameter filter interceptors for a given action, so, for example, + * you could use one in your default stack that filtered parameters + * you wanted blocked from every action and those you wanted blocked + * from an individual action you could add an additional interceptor + * for each action. + * + * + * + * + * + *

    + *
  • allowed - a comma delimited list of parameter prefixes + * that are allowed to pass to the action
  • + *
  • blocked - a comma delimited list of parameter prefixes + * that are not allowed to pass to the action
  • + *
  • defaultBlock - boolean (default to false) whether by + * default a given parameter is blocked. If true, then a parameter + * must have a prefix in the allowed list in order to be able + * to pass to the action + *
+ * + *

The way parameters are filtered for the least configuration is that + * if a string is in the allowed or blocked lists, then any parameter + * that is a member of the object represented by the parameter is allowed + * or blocked respectively.

+ * + *

For example, if the parameters are: + *

    + *
  • blocked: person,person.address.createDate,personDao
  • + *
  • allowed: person.address
  • + *
  • defaultBlock: false
  • + *
+ *
+ * The parameters person.name, person.phoneNum etc would be blocked + * because 'person' is in the blocked list. However, person.address.street + * and person.address.city would be allowed because person.address is + * in the allowed list (the longer string determines permissions).

+ * + * + * + * There are no known extension points to this interceptor. + * + * + *
+ * 
+ * <interceptors>
+ *   ...
+ *   <interceptor name="parameterFilter" class="com.opensymphony.xwork2.interceptor.ParameterFilterInterceptor"/>
+ *   ...
+ * </interceptors>
+ * 
+ * <action ....>
+ *   ...
+ *   <interceptor-ref name="parameterFilter">
+ *     <param name="blocked">person,person.address.createDate,personDao</param>
+ *   </interceptor-ref>
+ *   ...
+ * </action>
+ * 
+ * 
+ * + * @author Gabe + */ +public class ParameterFilterInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(ParameterFilterInterceptor.class); + + private Collection allowed; + private Collection blocked; + private Map includesExcludesMap; + private boolean defaultBlock = false; + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + + Map parameters = invocation.getInvocationContext().getParameters(); + HashSet paramsToRemove = new HashSet(); + + Map includesExcludesMap = getIncludesExcludesMap(); + + for (Object o : parameters.keySet()) { + String param = o.toString(); + + boolean currentAllowed = !isDefaultBlock(); + + boolean foundApplicableRule = false; + for (Object o1 : includesExcludesMap.keySet()) { + String currRule = (String) o1; + + if (param.startsWith(currRule) + && (param.length() == currRule.length() + || isPropSeperator(param.charAt(currRule.length())))) { + currentAllowed = includesExcludesMap.get(currRule).booleanValue(); + } else { + if (foundApplicableRule) { + foundApplicableRule = false; + break; + } + } + } + if (!currentAllowed) { + paramsToRemove.add(param); + } + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Params to remove: " + paramsToRemove); + } + + for (Object aParamsToRemove : paramsToRemove) { + parameters.remove(aParamsToRemove); + } + + return invocation.invoke(); + } + + /** + * Tests if the given char is a property seperator char .([. + * + * @param c the char + * @return true, if char is property separator, false otherwise. + */ + private static boolean isPropSeperator(char c) { + return c == '.' || c == '(' || c == '['; + } + + private Map getIncludesExcludesMap() { + if (this.includesExcludesMap == null) { + this.includesExcludesMap = new TreeMap(); + + if (getAllowedCollection() != null) { + for (String e : getAllowedCollection()) { + this.includesExcludesMap.put(e, Boolean.TRUE); + } + } + if (getBlockedCollection() != null) { + for (String b : getBlockedCollection()) { + this.includesExcludesMap.put(b, Boolean.FALSE); + } + } + } + + return this.includesExcludesMap; + } + + /** + * @return Returns the defaultBlock. + */ + public boolean isDefaultBlock() { + return defaultBlock; + } + + /** + * @param defaultExclude The defaultExclude to set. + */ + public void setDefaultBlock(boolean defaultExclude) { + this.defaultBlock = defaultExclude; + } + + /** + * @return Returns the blocked. + */ + public Collection getBlockedCollection() { + return blocked; + } + + /** + * @param blocked The blocked to set. + */ + public void setBlockedCollection(Collection blocked) { + this.blocked = blocked; + } + + /** + * @param blocked The blocked paramters as comma separated String. + */ + public void setBlocked(String blocked) { + setBlockedCollection(asCollection(blocked)); + } + + /** + * @return Returns the allowed. + */ + public Collection getAllowedCollection() { + return allowed; + } + + /** + * @param allowed The allowed to set. + */ + public void setAllowedCollection(Collection allowed) { + this.allowed = allowed; + } + + /** + * @param allowed The allowed paramters as comma separated String. + */ + public void setAllowed(String allowed) { + setAllowedCollection(asCollection(allowed)); + } + + /** + * Return a collection from the comma delimited String. + * + * @param commaDelim the comma delimited String. + * @return A collection from the comma delimited String. Returns null if the string is empty. + */ + private Collection asCollection(String commaDelim) { + if (commaDelim == null || commaDelim.trim().length() == 0) { + return null; + } + return TextParseUtil.commaDelimitedStringToSet(commaDelim); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java new file mode 100644 index 000000000..292dbeb83 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java @@ -0,0 +1,40 @@ +/* + * 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.interceptor; + +/** + * + * + * This interface is implemented by actions that want to declare acceptable parameters. Works in conjunction with {@link + * ParametersInterceptor}. For example, actions may want to create a whitelist of parameters they will accept or a + * blacklist of paramters they will reject to prevent clients from setting other unexpected (and possibly dangerous) + * parameters. + * + * + * + * @author Bob Lee (crazybob@google.com) + */ +public interface ParameterNameAware { + + /** + * Tests if the the action will accept the parameter with the given name. + * + * @param parameterName the parameter name + * @return if accepted, false otherwise + */ + boolean acceptableParameterName(String parameterName); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java new file mode 100644 index 000000000..dafec0ef1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java @@ -0,0 +1,145 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * + * This is a simple XWork interceptor that allows parameters (matching + * one of the paramNames attribute csv value) to be + * removed from the parameter map if they match a certain value + * (matching one of the paramValues attribute csv value), before they + * are set on the action. A typical usage would be to want a dropdown/select + * to map onto a boolean value on an action. The select had the options + * none, yes and no with values -1, true and false. The true and false would + * map across correctly. However the -1 would be set to false. + * This was not desired as one might needed the value on the action to stay null. + * This interceptor fixes this by preventing the parameter from ever reaching + * the action. + * + * + * + * + *
    + *
  • paramNames - A comma separated value (csv) indicating the parameter name + * whose param value should be considered that if they match any of the + * comma separated value (csv) from paramValues attribute, shall be + * removed from the parameter map such that they will not be applied + * to the action
  • + *
  • paramValues - A comma separated value (csv) indicating the parameter value that if + * matched shall have its parameter be removed from the parameter map + * such that they will not be applied to the action
  • + *
+ * + * + * + * + * No intended extension point + * + * + *
+ * 
+ *	
+ * <action name="sample" class="org.martingilday.Sample">
+ * 	<interceptor-ref name="paramRemover">
+ *   		<param name="paramNames">aParam,anotherParam</param>
+ *   		<param name="paramValues">--,-1</param>
+ * 	</interceptor-ref>
+ * 	<interceptor-ref name="defaultStack" />
+ * 	...
+ * </action>
+ *  
+ * 
+ * 
+ * + * + * @author martin.gilday + */ +public class ParameterRemoverInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(ParameterRemoverInterceptor.class); + + private static final long serialVersionUID = 1; + + private Set paramNames = Collections.emptySet(); + + private Set paramValues = Collections.emptySet(); + + + /** + * Decide if the parameter should be removed from the parameter map based on + * paramNames and paramValues. + * + * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor + */ + @Override + public String intercept(ActionInvocation invocation) throws Exception { + if (!(invocation.getAction() instanceof NoParameters) + && (null != this.paramNames)) { + ActionContext ac = invocation.getInvocationContext(); + final Map parameters = ac.getParameters(); + + if (parameters != null) { + for (String removeName : paramNames) { + // see if the field is in the parameter map + if (parameters.containsKey(removeName)) { + + try { + String[] values = (String[]) parameters + .get(removeName); + String value = values[0]; + if (null != value && this.paramValues.contains(value)) { + parameters.remove(removeName); + } + } catch (Exception e) { + LOG.error("Failed to convert parameter to string", e); + } + } + } + } + } + return invocation.invoke(); + } + + /** + * Allows paramNames attribute to be set as comma-separated-values (csv). + * + * @param paramNames the paramNames to set + */ + public void setParamNames(String paramNames) { + this.paramNames = TextParseUtil.commaDelimitedStringToSet(paramNames); + } + + + /** + * Allows paramValues attribute to be set as a comma-separated-values (csv). + * + * @param paramValues the paramValues to set + */ + public void setParamValues(String paramValues) { + this.paramValues = TextParseUtil.commaDelimitedStringToSet(paramValues); + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java new file mode 100644 index 000000000..bce32dbb2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java @@ -0,0 +1,436 @@ +/* + * 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.interceptor; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.MemberAccessValueStack; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; + + +/** + * + * This interceptor sets all parameters on the value stack. + *

+ * This interceptor gets all parameters from {@link ActionContext#getParameters()} and sets them on the value stack by + * calling {@link ValueStack#setValue(String, Object)}, typically resulting in the values submitted in a form + * request being applied to an action in the value stack. Note that the parameter map must contain a String key and + * often containers a String[] for the value. + *

+ *

The interceptor takes one parameter named 'ordered'. When set to true action properties are guaranteed to be + * set top-down which means that top action's properties are set first. Then it's subcomponents properties are set. + * The reason for this order is to enable a 'factory' pattern. For example, let's assume that one has an action + * that contains a property named 'modelClass' that allows to choose what is the underlying implementation of model. + * By assuring that modelClass property is set before any model properties are set, it's possible to choose model + * implementation during action.setModelClass() call. Similiarily it's possible to use action.setPrimaryKey() + * property set call to actually load the model class from persistent storage. Without any assumption on parameter + * order you have to use patterns like 'Preparable'. + *

+ *

Because parameter names are effectively OGNL statements, it is important that security be taken in to account. + * This interceptor will not apply any values in the parameters map if the expression contains an assignment (=), + * multiple expressions (,), or references any objects in the context (#). This is all done in the {@link + * #acceptableName(String)} method. In addition to this method, if the action being invoked implements the {@link + * ParameterNameAware} interface, the action will be consulted to determine if the parameter should be set. + *

+ *

In addition to these restrictions, a flag ({@link ReflectionContextState#DENY_METHOD_EXECUTION}) is set such that + * no methods are allowed to be invoked. That means that any expression such as person.doSomething() or + * person.getName() will be explicitely forbidden. This is needed to make sure that your application is not + * exposed to attacks by malicious users. + *

+ *

While this interceptor is being invoked, a flag ({@link ReflectionContextState#CREATE_NULL_OBJECTS}) is turned + * on to ensure that any null reference is automatically created - if possible. See the type conversion documentation + * and the {@link InstantiatingNullHandler} javadocs for more information. + *

+ *

Finally, a third flag ({@link XWorkConverter#REPORT_CONVERSION_ERRORS}) is set that indicates any errors when + * converting the the values to their final data type (String[] -> int) an unrecoverable error occured. With this + * flag set, the type conversion errors will be reported in the action context. See the type conversion documentation + * and the {@link XWorkConverter} javadocs for more information. + *

+ *

If you are looking for detailed logging information about your parameters, turn on DEBUG level logging for this + * interceptor. A detailed log of all the parameter keys and values will be reported. + *

+ *

+ * Note: Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being + * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor] + * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature. + *

+ * + *

+ *

Interceptor parameters: + *

+ * + *

+ *

    + *

    + *

  • ordered - set to true if you want the top-down property setter behaviour
  • + *

    + *

+ *

+ * + *

+ *

Extending the interceptor: + *

+ * + *

+ *

The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your + * actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend + * this interceptor and override the {@link #acceptableName(String)} method. + *

+ * + *

+ *

Example code: + *

+ *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="params"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Patrick Lightbody + */ +public class ParametersInterceptor extends MethodFilterInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(ParametersInterceptor.class); + + boolean ordered = false; + Set excludeParams = Collections.emptySet(); + Set acceptParams = Collections.emptySet(); + static boolean devMode = false; + + private String acceptedParamNames = "[[\\p{Graph}\\s]&&[^,#:=]]*"; + private Pattern acceptedPattern = Pattern.compile(acceptedParamNames); + + private ValueStackFactory valueStackFactory; + + @Inject + public void setValueStackFactory(ValueStackFactory valueStackFactory) { + this.valueStackFactory = valueStackFactory; + } + + @Inject("devMode") + public static void setDevMode(String mode) { + devMode = "true".equals(mode); + } + + public void setAcceptParamNames(String commaDelim) { + Collection acceptPatterns = asCollection(commaDelim); + if (acceptPatterns != null) { + acceptParams = new HashSet(); + for (String pattern : acceptPatterns) { + acceptParams.add(Pattern.compile(pattern)); + } + } + } + + /** + * Compares based on number of '.' characters (fewer is higher) + */ + static final Comparator rbCollator = new Comparator() { + public int compare(String s1, String s2) { + int l1 = 0, l2 = 0; + for (int i = s1.length() - 1; i >= 0; i--) { + if (s1.charAt(i) == '.') l1++; + } + for (int i = s2.length() - 1; i >= 0; i--) { + if (s2.charAt(i) == '.') l2++; + } + return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2)); + } + + }; + + @Override + public String doIntercept(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + if (!(action instanceof NoParameters)) { + ActionContext ac = invocation.getInvocationContext(); + final Map parameters = retrieveParameters(ac); + + if (LOG.isDebugEnabled()) { + LOG.debug("Setting params " + getParameterLogMap(parameters)); + } + + if (parameters != null) { + Map contextMap = ac.getContextMap(); + try { + ReflectionContextState.setCreatingNullObjects(contextMap, true); + ReflectionContextState.setDenyMethodExecution(contextMap, true); + ReflectionContextState.setReportingConversionErrors(contextMap, true); + + ValueStack stack = ac.getValueStack(); + setParameters(action, stack, parameters); + } finally { + ReflectionContextState.setCreatingNullObjects(contextMap, false); + ReflectionContextState.setDenyMethodExecution(contextMap, false); + ReflectionContextState.setReportingConversionErrors(contextMap, false); + } + } + } + return invocation.invoke(); + } + + /** + * Gets the parameter map to apply from wherever appropriate + * + * @param ac The action context + * @return The parameter map to apply + */ + protected Map retrieveParameters(ActionContext ac) { + return ac.getParameters(); + } + + + /** + * Adds the parameters into context's ParameterMap + * + * @param ac The action context + * @param newParams The parameter map to apply + *

+ * In this class this is a no-op, since the parameters were fetched from the same location. + * In subclasses both retrieveParameters() and addParametersToContext() should be overridden. + */ + protected void addParametersToContext(ActionContext ac, Map newParams) { + } + + protected void setParameters(Object action, ValueStack stack, final Map parameters) { + ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware) + ? (ParameterNameAware) action : null; + + Map params; + Map acceptableParameters; + if (ordered) { + params = new TreeMap(getOrderedComparator()); + acceptableParameters = new TreeMap(getOrderedComparator()); + params.putAll(parameters); + } else { + params = new TreeMap(parameters); + acceptableParameters = new TreeMap(); + } + + for (Map.Entry entry : params.entrySet()) { + String name = entry.getKey(); + + boolean acceptableName = acceptableName(name) + && (parameterNameAware == null + || parameterNameAware.acceptableParameterName(name)); + + if (acceptableName) { + acceptableParameters.put(name, entry.getValue()); + } + } + + ValueStack newStack = valueStackFactory.createValueStack(stack); + boolean clearableStack = newStack instanceof ClearableValueStack; + if (clearableStack) { + //if the stack's context can be cleared, do that to prevent OGNL + //from having access to objects in the stack, see XW-641 + ((ClearableValueStack)newStack).clearContextValues(); + Map context = newStack.getContext(); + ReflectionContextState.setCreatingNullObjects(context, true); + ReflectionContextState.setDenyMethodExecution(context, true); + ReflectionContextState.setReportingConversionErrors(context, true); + + //keep locale from original context + context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE)); + } + + boolean memberAccessStack = newStack instanceof MemberAccessValueStack; + if (memberAccessStack) { + //block or allow access to properties + //see WW-2761 for more details + MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack; + accessValueStack.setAcceptProperties(acceptParams); + accessValueStack.setExcludeProperties(excludeParams); + } + + for (Map.Entry entry : acceptableParameters.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + try { + newStack.setValue(name, value); + } catch (RuntimeException e) { + if (devMode) { + String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{ + "Unexpected Exception caught setting '" + name + "' on '" + action.getClass() + ": " + e.getMessage() + }); + LOG.error(developerNotification); + if (action instanceof ValidationAware) { + ((ValidationAware) action).addActionMessage(developerNotification); + } + } + } + } + + if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null)) + stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS)); + + addParametersToContext(ActionContext.getContext(), acceptableParameters); + } + + /** + * Gets an instance of the comparator to use for the ordered sorting. Override this + * method to customize the ordering of the parameters as they are set to the + * action. + * + * @return A comparator to sort the parameters + */ + protected Comparator getOrderedComparator() { + return rbCollator; + } + + private String getParameterLogMap(Map parameters) { + if (parameters == null) { + return "NONE"; + } + + StringBuilder logEntry = new StringBuilder(); + for (Map.Entry entry : parameters.entrySet()) { + logEntry.append(String.valueOf(entry.getKey())); + logEntry.append(" => "); + if (entry.getValue() instanceof Object[]) { + Object[] valueArray = (Object[]) entry.getValue(); + logEntry.append("[ "); + if (valueArray.length > 0 ) { + for (int indexA = 0; indexA < (valueArray.length - 1); indexA++) { + Object valueAtIndex = valueArray[indexA]; + logEntry.append(String.valueOf(valueAtIndex)); + logEntry.append(", "); + } + logEntry.append(String.valueOf(valueArray[valueArray.length - 1])); + } + logEntry.append(" ] "); + } else { + logEntry.append(String.valueOf(entry.getValue())); + } + } + + return logEntry.toString(); + } + + protected boolean acceptableName(String name) { + if (isAccepted(name) && !isExcluded(name)) { + return true; + } + return false; + } + + protected boolean isAccepted(String paramName) { + if (!this.acceptParams.isEmpty()) { + for (Pattern pattern : acceptParams) { + Matcher matcher = pattern.matcher(paramName); + if (matcher.matches()) { + return true; + } + } + return false; + } else + return acceptedPattern.matcher(paramName).matches(); + } + + protected boolean isExcluded(String paramName) { + if (!this.excludeParams.isEmpty()) { + for (Pattern pattern : excludeParams) { + Matcher matcher = pattern.matcher(paramName); + if (matcher.matches()) { + return true; + } + } + } + return false; + } + + /** + * Whether to order the parameters or not + * + * @return True to order + */ + public boolean isOrdered() { + return ordered; + } + + /** + * Set whether to order the parameters by object depth or not + * + * @param ordered True to order them + */ + public void setOrdered(boolean ordered) { + this.ordered = ordered; + } + + /** + * Gets a set of regular expressions of parameters to remove + * from the parameter map + * + * @return A set of compiled regular expression patterns + */ + protected Set getExcludeParamsSet() { + return excludeParams; + } + + /** + * Sets a comma-delimited list of regular expressions to match + * parameters that should be removed from the parameter map. + * + * @param commaDelim A comma-delimited list of regular expressions + */ + public void setExcludeParams(String commaDelim) { + Collection excludePatterns = asCollection(commaDelim); + if (excludePatterns != null) { + excludeParams = new HashSet(); + for (String pattern : excludePatterns) { + excludeParams.add(Pattern.compile(pattern)); + } + } + } + + /** + * Return a collection from the comma delimited String. + * + * @param commaDelim the comma delimited String. + * @return A collection from the comma delimited String. Returns null if the string is empty. + */ + private Collection asCollection(String commaDelim) { + if (commaDelim == null || commaDelim.trim().length() == 0) { + return null; + } + return TextParseUtil.commaDelimitedStringToSet(commaDelim); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java new file mode 100644 index 000000000..12643a3d2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java @@ -0,0 +1,39 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; + + +/** + * PreResultListeners may be registered with an {@link ActionInvocation} to get a callback after the + * {@link com.opensymphony.xwork2.Action} has been executed but before the {@link com.opensymphony.xwork2.Result} + * is executed. + * + * @author Jason Carreira + */ +public interface PreResultListener { + + /** + * This callback method will be called after the {@link com.opensymphony.xwork2.Action} execution and + * before the {@link com.opensymphony.xwork2.Result} execution. + * + * @param invocation the action invocation + * @param resultCode the result code returned by the action (eg. success). + */ + void beforeResult(ActionInvocation invocation, String resultCode); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java new file mode 100644 index 000000000..30e543f8f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java @@ -0,0 +1,170 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A utility class for invoking prefixed methods in action class. + * + * Interceptors that made use of this class are: + *

    + *
  • DefaultWorkflowInterceptor
  • + *
  • PrepareInterceptor
  • + *
+ * + *

+ * + * + * + * In DefaultWorkflowInterceptor + *

applies only when action implements {@link com.opensymphony.xwork2.Validateable}

+ *
    + *
  1. if the action class have validate{MethodName}(), it will be invoked
  2. + *
  3. else if the action class have validateDo{MethodName}(), it will be invoked
  4. + *
  5. no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is "true" (which is by default "true"), validate() will be invoked.
  6. + *
+ * + * + * + * + * + * + * In PrepareInterceptor + *

Applies only when action implements Preparable

+ *
    + *
  1. if the action class have prepare{MethodName}(), it will be invoked
  2. + *
  3. else if the action class have prepareDo(MethodName()}(), it will be invoked
  4. + *
  5. no matter if 1] or 2] is performed, if alwaysinvokePrepare property of the interceptor is "true" (which is by default "true"), prepare() will be invoked.
  6. + *
+ * + * + * + * @author Philip Luppens + * @author tm_jee + */ +public class PrefixMethodInvocationUtil { + + private static final Logger LOG = LoggerFactory.getLogger(PrefixMethodInvocationUtil.class); + + private static final String DEFAULT_INVOCATION_METHODNAME = "execute"; + + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + /** + * This method will prefix actionInvocation's ActionProxy's + * method with prefixes before invoking the prefixed method. + * Order of the prefixes is important, as this method will return once + * a prefixed method is found in the action class. + * + *

+ * + * For example, with + *

+	 *   invokePrefixMethod(actionInvocation, new String[] { "prepare", "prepareDo" });
+	 * 
+ * + * Assuming actionInvocation.getProxy(),getMethod() returns "submit", + * the order of invocation would be as follows:- + *
    + *
  1. prepareSubmit()
  2. + *
  3. prepareDoSubmit()
  4. + *
+ * + * If prepareSubmit() exists, it will be invoked and this method + * will return, prepareDoSubmit() will NOT be invoked. + * + *

+ * + * On the other hand, if prepareDoSubmit() does not exists, and + * prepareDoSubmit() exists, it will be invoked. + * + *

+ * + * If none of those two methods exists, nothing will be invoked. + * + * @param actionInvocation the action invocation + * @param prefixes prefixes for method names + * @throws InvocationTargetException is thrown if invocation of a method failed. + * @throws IllegalAccessException is thrown if invocation of a method failed. + */ + public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException { + Object action = actionInvocation.getAction(); + + String methodName = actionInvocation.getProxy().getMethod(); + + if (methodName == null) { + // if null returns (possible according to the docs), use the default execute + methodName = DEFAULT_INVOCATION_METHODNAME; + } + + Method method = getPrefixedMethod(prefixes, methodName, action); + if (method != null) { + method.invoke(action, new Object[0]); + } + } + + + /** + * This method returns a {@link Method} in action. The method + * returned is found by searching for method in action whose method name + * is equals to the result of appending each prefixes + * to methodName. Only the first method found will be returned, hence + * the order of prefixes is important. If none is found this method + * will return null. + * + * @param prefixes the prefixes to prefix the methodName + * @param methodName the method name to be prefixed with prefixes + * @param action the action class of which the prefixed method is to be search for. + * @return a {@link Method} if one is found, else null. + */ + public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) { + assert(prefixes != null); + String capitalizedMethodName = capitalizeMethodName(methodName); + for (String prefixe : prefixes) { + String prefixedMethodName = prefixe + capitalizedMethodName; + try { + return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY); + } + catch (NoSuchMethodException e) { + // hmm -- OK, try next prefix + if (LOG.isDebugEnabled()) { + LOG.debug("cannot find method [" + prefixedMethodName + "] in action [" + action + "]"); + } + } + } + return null; + } + + /** + * This method capitalized the first character of methodName. + *
+ * eg. capitalizeMethodName("someMethod"); will return "SomeMethod". + * + * @param methodName the method name + * @return capitalized method name + */ + public static String capitalizeMethodName(String methodName) { + assert(methodName != null); + return methodName.substring(0, 1).toUpperCase() + methodName.substring(1); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java new file mode 100644 index 000000000..5c627656e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java @@ -0,0 +1,152 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.Preparable; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; + + +/** + * + * + * This interceptor calls prepare() on actions which implement + * {@link Preparable}. This interceptor is very useful for any situation where + * you need to ensure some logic runs before the actual execute method runs. + * + *

A typical use of this is to run some logic to load an object from the + * database so that when parameters are set they can be set on this object. For + * example, suppose you have a User object with two properties: id and + * name. Provided that the params interceptor is called twice (once + * before and once after this interceptor), you can load the User object using + * the id property, and then when the second params interceptor is called the + * parameter user.name will be set, as desired, on the actual object + * loaded from the database. See the example for more info. + * + *

+ * Note: Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being + * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor] + * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature. + * + *

Update: Added logic to execute a prepare{MethodName} and conditionally + * the a general prepare() Method, depending on the 'alwaysInvokePrepare' parameter/property + * which is by default true. This allows us to run some logic based on the method + * name we specify in the {@link com.opensymphony.xwork2.ActionProxy}. For example, you can specify a + * prepareInput() method that will be run before the invocation of the input method. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • alwaysInvokePrepare - Default to true. If true, prepare will always be invoked, + * otherwise it will not.
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * There are no known extension points to this interceptor. + * + * + * + *

Example code: + * + *

+ * 
+ * <!-- Calls the params interceptor twice, allowing you to
+ *       pre-load data for the second time parameters are set -->
+ *  <action name="someAction" class="com.examples.SomeAction">
+ *      <interceptor-ref name="params"/>
+ *      <interceptor-ref name="prepare"/>
+ *      <interceptor-ref name="basicStack"/>
+ *      <result name="success">good_result.ftl</result>
+ *  </action>
+ * 
+ * 
+ * + * @author Jason Carreira + * @author Philip Luppens + * @author tm_jee + * @see com.opensymphony.xwork2.Preparable + */ +public class PrepareInterceptor extends MethodFilterInterceptor { + + private static final long serialVersionUID = -5216969014510719786L; + + private static final Logger LOG = LoggerFactory.getLogger(PrepareInterceptor.class); + + private final static String PREPARE_PREFIX = "prepare"; + private final static String ALT_PREPARE_PREFIX = "prepareDo"; + + private boolean alwaysInvokePrepare = true; + + /** + * Sets if the preapare method should always be executed. + *

+ * Default is true. + * + * @param alwaysInvokePrepare if prepare should always be executed or not. + */ + public void setAlwaysInvokePrepare(String alwaysInvokePrepare) { + this.alwaysInvokePrepare = Boolean.parseBoolean(alwaysInvokePrepare); + } + + @Override + public String doIntercept(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + + if (action instanceof Preparable) { + try { + PrefixMethodInvocationUtil.invokePrefixMethod(invocation, + new String[]{PREPARE_PREFIX, ALT_PREPARE_PREFIX}); + } + catch (InvocationTargetException e) { + // just in case there's an exception while doing reflection, + // we still want prepare() to be able to get called. + LOG.warn("an exception occured while trying to execute prefixed method", e); + } + catch (IllegalAccessException e) { + // just in case there's an exception while doing reflection, + // we still want prepare() to be able to get called. + LOG.warn("an exception occured while trying to execute prefixed method", e); + } catch (Exception e) { + // just in case there's an exception while doing reflection, + // we still want prepare() to be able to get called. + LOG.warn("an exception occured while trying to execute prefixed method", e); + } + + if (alwaysInvokePrepare) { + ((Preparable) action).prepare(); + } + } + + return invocation.invoke(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java new file mode 100644 index 000000000..e38137375 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.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.interceptor; + +import com.opensymphony.xwork2.ModelDriven; + +/** + * Adds the ability to set a model, probably retrieved from a given state. + */ +public interface ScopedModelDriven extends ModelDriven { + + /** + * Sets the model + */ + void setModel(T model); + + /** + * Sets the key under which the model is stored + * @param key The model key + */ + void setScopeKey(String key); + + /** + * Gets the key under which the model is stored + */ + String getScopeKey(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java new file mode 100644 index 000000000..8cf10acb3 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java @@ -0,0 +1,164 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.inject.Inject; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * + * + * An interceptor that enables scoped model-driven actions. + * + *

This interceptor only activates on actions that implement the {@link ScopedModelDriven} interface. If + * detected, it will retrieve the model class from the configured scope, then provide it to the Action. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • className - The model class name. Defaults to the class name of the object returned by the getModel() method.
  • + * + *
  • name - The key to use when storing or retrieving the instance in a scope. Defaults to the model + * class name.
  • + * + *
  • scope - The scope to store and retrieve the model. Defaults to 'request' but can also be 'session'.
  • + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * There are no known extension points for this interceptor. + * + * + * + *

Example code: + * + *

+ * 
+ * 
+ * <-- Basic usage -->
+ * <interceptor name="scopedModelDriven" class="com.opensymphony.interceptor.ScopedModelDrivenInterceptor" />
+ * 
+ * <-- Using all available parameters -->
+ * <interceptor name="gangsterForm" class="com.opensymphony.interceptor.ScopedModelDrivenInterceptor">
+ *      <param name="scope">session</param>
+ *      <param name="name">gangsterForm</param>
+ *      <param name="className">com.opensymphony.example.GangsterForm</param>
+ *  </interceptor>
+ * 
+ * 
+ * 
+ */ +public class ScopedModelDrivenInterceptor extends AbstractInterceptor { + + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + private static final String GET_MODEL = "getModel"; + private String scope; + private String name; + private String className; + private ObjectFactory objectFactory; + + @Inject + public void setObjectFactory(ObjectFactory factory) { + this.objectFactory = factory; + } + + protected Object resolveModel(ObjectFactory factory, ActionContext actionContext, String modelClassName, String modelScope, String modelName) throws Exception { + Object model = null; + Map scopeMap = actionContext.getContextMap(); + if ("session".equals(modelScope)) { + scopeMap = actionContext.getSession(); + } + + model = scopeMap.get(modelName); + if (model == null) { + model = factory.buildBean(modelClassName, null); + scopeMap.put(modelName, model); + } + return model; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + + if (action instanceof ScopedModelDriven) { + ScopedModelDriven modelDriven = (ScopedModelDriven) action; + if (modelDriven.getModel() == null) { + ActionContext ctx = ActionContext.getContext(); + ActionConfig config = invocation.getProxy().getConfig(); + + String cName = className; + if (cName == null) { + try { + Method method = action.getClass().getMethod(GET_MODEL, EMPTY_CLASS_ARRAY); + Class cls = method.getReturnType(); + cName = cls.getName(); + } catch (NoSuchMethodException e) { + throw new XWorkException("The " + GET_MODEL + "() is not defined in action " + action.getClass() + "", config); + } + } + String modelName = name; + if (modelName == null) { + modelName = cName; + } + Object model = resolveModel(objectFactory, ctx, cName, scope, modelName); + modelDriven.setModel(model); + modelDriven.setScopeKey(modelName); + } + } + return invocation.invoke(); + } + + /** + * @param className the className to set + */ + public void setClassName(String className) { + this.className = className; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @param scope the scope to set + */ + public void setScope(String scope) { + this.scope = scope; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java new file mode 100644 index 000000000..ed9659003 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java @@ -0,0 +1,241 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.Parameterizable; +import com.opensymphony.xwork2.util.*; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + + +/** + * + * + * This interceptor populates the action with the static parameters defined in the action configuration. If the action + * implements {@link Parameterizable}, a map of the static parameters will be also be passed directly to the action. + * The static params will be added to the request params map, unless "merge" is set to false. + * + *

Parameters are typically defined with <param> elements within xwork.xml. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • None
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + * + * + *

There are no extension points to this interceptor. + * + * + * + *

Example code: + * + *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="staticParams">
+ *          <param name="parse">true</param>
+ *          <param name="overwrite">false</param>
+ *     </interceptor-ref>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Patrick Lightbody + */ +public class StaticParametersInterceptor extends AbstractInterceptor { + + private boolean parse; + private boolean overwrite; + private boolean merge = true; + + static boolean devMode = false; + + private static final Logger LOG = LoggerFactory.getLogger(StaticParametersInterceptor.class); + + private ValueStackFactory valueStackFactory; + + @Inject + public void setValueStackFactory(ValueStackFactory valueStackFactory) { + this.valueStackFactory = valueStackFactory; + } + + @Inject("devMode") + public static void setDevMode(String mode) { + devMode = "true".equals(mode); + } + + public void setParse(String value) { + this.parse = Boolean.valueOf(value).booleanValue(); + } + + public void setMerge(String value) { + this.merge = Boolean.valueOf(value).booleanValue(); + } + + /** + * Overwrites already existing parameters from other sources. + * Static parameters are the successor over previously set parameters, if true. + * + * @param value + */ + public void setOverwrite(String value) { + this.overwrite = Boolean.valueOf(value).booleanValue(); + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + ActionConfig config = invocation.getProxy().getConfig(); + Object action = invocation.getAction(); + + final Map parameters = config.getParams(); + + if (LOG.isDebugEnabled()) { + LOG.debug("Setting static parameters " + parameters); + } + + // for actions marked as Parameterizable, pass the static parameters directly + if (action instanceof Parameterizable) { + ((Parameterizable) action).setParams(parameters); + } + + if (parameters != null) { + ActionContext ac = ActionContext.getContext(); + Map contextMap = ac.getContextMap(); + try { + ReflectionContextState.setCreatingNullObjects(contextMap, true); + ReflectionContextState.setReportingConversionErrors(contextMap, true); + final ValueStack stack = ac.getValueStack(); + + ValueStack newStack = valueStackFactory.createValueStack(stack); + boolean clearableStack = newStack instanceof ClearableValueStack; + if (clearableStack) { + //if the stack's context can be cleared, do that to prevent OGNL + //from having access to objects in the stack, see XW-641 + ((ClearableValueStack)newStack).clearContextValues(); + Map context = newStack.getContext(); + ReflectionContextState.setCreatingNullObjects(context, true); + ReflectionContextState.setDenyMethodExecution(context, true); + ReflectionContextState.setReportingConversionErrors(context, true); + + //keep locale from original context + context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE)); + } + + for (Map.Entry entry : parameters.entrySet()) { + Object val = entry.getValue(); + if (parse && val instanceof String) { + val = TextParseUtil.translateVariables(val.toString(), stack); + } + try { + newStack.setValue(entry.getKey(), val); + } catch (RuntimeException e) { + if (devMode) { + String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{ + "Unexpected Exception caught setting '" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage() + }); + LOG.error(developerNotification); + if (action instanceof ValidationAware) { + ((ValidationAware) action).addActionMessage(developerNotification); + } + } + } + } + + if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null)) + stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS)); + + if (merge) + addParametersToContext(ac, parameters); + } finally { + ReflectionContextState.setCreatingNullObjects(contextMap, false); + ReflectionContextState.setReportingConversionErrors(contextMap, false); + } + } + return invocation.invoke(); + } + + + /** + * @param ac The action context + * @return the parameters from the action mapping in the context. If none found, returns + * an empty map. + */ + protected Map retrieveParameters(ActionContext ac) { + ActionConfig config = ac.getActionInvocation().getProxy().getConfig(); + if (config != null) { + return config.getParams(); + } else { + return Collections.emptyMap(); + } + } + + /** + * Adds the parameters into context's ParameterMap. + * As default, static parameters will not overwrite existing paramaters from other sources. + * If you want the static parameters as successor over already existing parameters, set overwrite to true. + * + * @param ac The action context + * @param newParams The parameter map to apply + */ + protected void addParametersToContext(ActionContext ac, Map newParams) { + Map previousParams = ac.getParameters(); + + Map combinedParams; + if ( overwrite ) { + if (previousParams != null) { + combinedParams = new TreeMap(previousParams); + } else { + combinedParams = new TreeMap(); + } + if ( newParams != null) { + combinedParams.putAll(newParams); + } + } else { + if (newParams != null) { + combinedParams = new TreeMap(newParams); + } else { + combinedParams = new TreeMap(); + } + if ( previousParams != null) { + combinedParams.putAll(previousParams); + } + } + ac.setParameters(combinedParams); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/TimerInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/TimerInterceptor.java new file mode 100644 index 000000000..ad53d80e4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/TimerInterceptor.java @@ -0,0 +1,243 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + * + * This interceptor logs the amount of time in milliseconds. In order for this interceptor to work properly, the + * logging framework must be set to at least the INFO level. + * This interceptor relies on the Commons Logging API to + * report its execution-time value. + * + * + * + * + *
    + * + *
  • logLevel (optional) - what log level should we use (trace, debug, info, warn, error, fatal)? - defaut is info
  • + * + *
  • logCategory (optional) - If provided we would use this category (eg. com.mycompany.app). + * Default is to use com.opensymphony.xwork2.interceptor.TimerInterceptor.
  • + * + *
+ * + * The parameters above enables us to log all action execution times in our own logfile. + * + * + * + * + * This interceptor can be extended to provide custom message format. Users should override the + * invokeUnderTiming method. + * + * + *
+ * 
+ * <!-- records only the action's execution time -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="completeStack"/>
+ *     <interceptor-ref name="timer"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <!-- records action's execution time as well as other interceptors-->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="timer"/>
+ *     <interceptor-ref name="completeStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * This second example uses our own log category at debug level. + * + *
+ * 
+ * <!-- records only the action's execution time -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="completeStack"/>
+ *     <interceptor-ref name="timer">
+ *         <param name="logLevel">debug</param>
+ *         <param name="logCategory">com.mycompany.myapp.actiontime</param>
+ *     <interceptor-ref/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <!-- records action's execution time as well as other interceptors-->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="timer"/>
+ *     <interceptor-ref name="completeStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Jason Carreira + * @author Claus Ibsen + */ +public class TimerInterceptor extends AbstractInterceptor { + protected static final Logger LOG = LoggerFactory.getLogger(TimerInterceptor.class); + + protected Logger categoryLogger; + protected String logCategory; + protected String logLevel; + + public String getLogCategory() { + return logCategory; + } + + public void setLogCategory(String logCatgory) { + this.logCategory = logCatgory; + } + + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + if (! shouldLog()) { + return invocation.invoke(); + } else { + return invokeUnderTiming(invocation); + } + } + + /** + * Is called to invoke the action invocation and time the execution time. + * + * @param invocation the action invocation. + * @return the result of the action execution. + * @throws Exception can be thrown from the action. + */ + protected String invokeUnderTiming(ActionInvocation invocation) throws Exception { + long startTime = System.currentTimeMillis(); + String result = invocation.invoke(); + long executionTime = System.currentTimeMillis() - startTime; + + StringBuilder message = new StringBuilder(100); + message.append("Executed action ["); + String namespace = invocation.getProxy().getNamespace(); + if ((namespace != null) && (namespace.trim().length() > 0)) { + message.append(namespace).append("/"); + } + message.append(invocation.getProxy().getActionName()); + message.append("!"); + message.append(invocation.getProxy().getMethod()); + message.append("] took ").append(executionTime).append(" ms."); + + doLog(getLoggerToUse(), message.toString()); + + return result; + } + + /** + * Determines if we should log the time. + * + * @return true to log, false to not log. + */ + protected boolean shouldLog() { + // default check first + if (logLevel == null && logCategory == null) { + return LOG.isInfoEnabled(); + } + + // okay user have set some parameters + return isLoggerEnabled(getLoggerToUse(), logLevel); + } + + /** + * Get's the logger to use. + * + * @return the logger to use. + */ + protected Logger getLoggerToUse() { + if (logCategory != null) { + if (categoryLogger == null) { + // init category logger + categoryLogger = LoggerFactory.getLogger(logCategory); + if (logLevel == null) { + logLevel = "info"; // use info as default if not provided + } + } + return categoryLogger; + } + + return LOG; + } + + /** + * Performs the actual logging. + * + * @param logger the provided logger to use. + * @param message the message to log. + */ + protected void doLog(Logger logger, String message) { + if (logLevel == null) { + logger.info(message); + return; + } + + if ("debug".equalsIgnoreCase(logLevel)) { + logger.debug(message); + } else if ("info".equalsIgnoreCase(logLevel)) { + logger.info(message); + } else if ("warn".equalsIgnoreCase(logLevel)) { + logger.warn(message); + } else if ("error".equalsIgnoreCase(logLevel)) { + logger.error(message); + } else if ("fatal".equalsIgnoreCase(logLevel)) { + logger.fatal(message); + } else if ("trace".equalsIgnoreCase(logLevel)) { + logger.trace(message); + } else { + throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported"); + } + } + + /** + * Is the given logger enalbed at the given level? + * + * @param logger the logger. + * @param level the level to check if isXXXEnabled. + * @return true if enabled, false if not. + */ + private static boolean isLoggerEnabled(Logger logger, String level) { + if ("debug".equalsIgnoreCase(level)) { + return logger.isDebugEnabled(); + } else if ("info".equalsIgnoreCase(level)) { + return logger.isInfoEnabled(); + } else if ("warn".equalsIgnoreCase(level)) { + return logger.isWarnEnabled(); + } else if ("error".equalsIgnoreCase(level)) { + return logger.isErrorEnabled(); + } else if ("fatal".equalsIgnoreCase(level)) { + return logger.isFatalEnabled(); + } else if ("trace".equalsIgnoreCase(level)) { + return logger.isTraceEnabled(); + } else { + throw new IllegalArgumentException("LogLevel [" + level + "] is not supported"); + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java new file mode 100644 index 000000000..b5f2a5509 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java @@ -0,0 +1,9 @@ +package com.opensymphony.xwork2.interceptor; + +/** + * ValidationWorkflowAware + */ +public interface ValidationWorkflowAware { + + String getInputResultName(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/After.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/After.java new file mode 100644 index 000000000..3fc2fcd8e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/After.java @@ -0,0 +1,81 @@ +/* + * 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.interceptor.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * Marks a action method that needs to be called after the main action method and the result was + * executed. Return value is ignored. + * + * + *

Annotation usage: + * + * + * The After annotation can be applied at method level. + * + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
priorityno10Priority order of method execution
+ * + * + *

Example code: + * + *

+ * 
+ * public class SampleAction extends ActionSupport {
+ *
+ *  @After
+ *  public void isValid() throws ValidationException {
+ *    // validate model object, throw exception if failed
+ *  }
+ *
+ *  public String execute() {
+ *     // perform action
+ *     return SUCCESS;
+ *  }
+ * }
+ * 
+ * 
+ * + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface After { + int priority() default 10; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Allowed.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Allowed.java new file mode 100644 index 000000000..42633444e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Allowed.java @@ -0,0 +1,18 @@ +package com.opensymphony.xwork2.interceptor.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares that it is permitted for the field be mutated through + * a HttpRequest parameter. + * + * @author martin.gilday + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Allowed { + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationParameterFilterIntereptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationParameterFilterIntereptor.java new file mode 100644 index 000000000..528a65f94 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationParameterFilterIntereptor.java @@ -0,0 +1,84 @@ +package com.opensymphony.xwork2.interceptor.annotations; + + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.interceptor.Interceptor; +import com.opensymphony.xwork2.interceptor.ParameterFilterInterceptor; +import com.opensymphony.xwork2.interceptor.ParametersInterceptor; +import com.opensymphony.xwork2.util.AnnotationUtils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * Annotation based version of {@link ParameterFilterInterceptor}. + *

+ * This {@link Interceptor} must be placed in the stack before the {@link ParametersInterceptor} + * When a parameter matches a field that is marked {@link Blocked} then it is removed from + * the parameter map. + *

+ * If an {@link Action} class is marked with {@link BlockByDefault} then all parameters are + * removed unless a field on the Action exists and is marked with {@link Allowed} + * + * @author martin.gilday + */ +public class AnnotationParameterFilterIntereptor extends AbstractInterceptor { + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation) + */ + @Override public String intercept(ActionInvocation invocation) throws Exception { + + final Object action = invocation.getAction(); + Map parameters = invocation.getInvocationContext().getParameters(); + + boolean blockByDefault = action.getClass().isAnnotationPresent(BlockByDefault.class); + List annotatedFields = new ArrayList(); + HashSet paramsToRemove = new HashSet(); + + if (blockByDefault) { + AnnotationUtils.addAllFields(Allowed.class, action.getClass(), annotatedFields); + + for (String paramName : parameters.keySet()) { + boolean allowed = false; + + for (Field field : annotatedFields) { + //TODO only matches exact field names. need to change to it matches start of ognl expression + //i.e take param name up to first . (period) and match against that + if (field.getName().equals(paramName)) { + allowed = true; + } + } + + if (!allowed) { + paramsToRemove.add(paramName); + } + } + } else { + AnnotationUtils.addAllFields(Blocked.class, action.getClass(), annotatedFields); + + for (String paramName : parameters.keySet()) { + + for (Field field : annotatedFields) { + //TODO only matches exact field names. need to change to it matches start of ognl expression + //i.e take param name up to first . (period) and match against that + if (field.getName().equals(paramName)) { + paramsToRemove.add(paramName); + } + } + } + } + + for (String aParamsToRemove : paramsToRemove) { + parameters.remove(aParamsToRemove); + } + + return invocation.invoke(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptor.java new file mode 100644 index 000000000..118d8ac2f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptor.java @@ -0,0 +1,199 @@ +/* + * 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.interceptor.annotations; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.interceptor.Interceptor; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.AnnotationUtils; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * + *

Invokes any annotated methods on the action. Specifically, it supports the following + * annotations: + *

    + *
  • @{@link Before} - will be invoked before the action method. If the returned value is not null, it is + * returned as the action result code
  • + *
  • @{@link BeforeResult} - will be invoked after the action method but before the result execution
  • + *
  • @{@link After} - will be invoked after the action method and result execution
  • + *
+ *

+ *

+ *

There can be multiple methods marked with the same annotations, but the order of their execution + * is not guaranteed. However, the annotated methods on the superclass chain are guaranteed to be invoked before the + * annotated method in the current class in the case of a {@link Before} annotations and after, if the annotations is + * {@link After}.

+ * + *

+ *

+ * 
+ *  public class BaseAnnotatedAction {
+ *  	protected String log = "";
+ * 

+ * @Before + * public String baseBefore() { + * log = log + "baseBefore-"; + * return null; + * } + * } + *

+ * public class AnnotatedAction extends BaseAnnotatedAction { + * @Before + * public String before() { + * log = log + "before"; + * return null; + * } + *

+ * public String execute() { + * log = log + "-execute"; + * return Action.SUCCESS; + * } + *

+ * @BeforeResult + * public void beforeResult() throws Exception { + * log = log +"-beforeResult"; + * } + *

+ * @After + * public void after() { + * log = log + "-after"; + * } + * } + * + *

+ *

+ * + *

With the interceptor applied and the action executed on AnnotatedAction the log + * instance variable will contain baseBefore-before-execute-beforeResult-after.

+ * + *

+ *

Configure a stack in xwork.xml that replaces the PrepareInterceptor with the AnnotationWorkflowInterceptor: + *

+ * 
+ * <interceptor-stack name="annotatedStack">
+ * 	<interceptor-ref name="staticParams"/>
+ * 	<interceptor-ref name="params"/>
+ * 	<interceptor-ref name="conversionError"/>
+ * 	<interceptor-ref name="annotationWorkflow"/>
+ * </interceptor-stack>
+ *  
+ * 
+ * + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + * @author Dan Oxlade, dan d0t oxlade at gmail d0t c0m + */ +public class AnnotationWorkflowInterceptor implements Interceptor, PreResultListener { + + /** + * Discovers annotated methods on the action and calls them according to the workflow + * + * @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation) + */ + public String intercept(ActionInvocation invocation) throws Exception { + final Object action = invocation.getAction(); + invocation.addPreResultListener(this); + List methods = new ArrayList(AnnotationUtils.getAnnotatedMethods(action.getClass(), Before.class)); + if (methods.size() > 0) { + // methods are only sorted by priority + Collections.sort(methods, new Comparator() { + public int compare(Method method1, Method method2) { + return comparePriorities(method1.getAnnotation(Before.class).priority(), + method2.getAnnotation(Before.class).priority()); + } + }); + for (Method m : methods) { + final String resultCode = (String) m + .invoke(action, (Object[]) null); + if (resultCode != null) { + // shortcircuit execution + return resultCode; + } + } + } + + String invocationResult = invocation.invoke(); + + // invoke any @After methods + methods = new ArrayList(AnnotationUtils.getAnnotatedMethods(action.getClass(), After.class)); + + if (methods.size() > 0) { + // methods are only sorted by priority + Collections.sort(methods, new Comparator() { + public int compare(Method method1, Method method2) { + return comparePriorities(method1.getAnnotation(After.class).priority(), + method2.getAnnotation(After.class).priority()); + } + }); + for (Method m : methods) { + m.invoke(action, (Object[]) null); + } + } + + return invocationResult; + } + + public void destroy() { + } + + public void init() { + } + + protected static int comparePriorities(int val1, int val2) { + if (val2 < val1) { + return -1; + } else if (val2 > val1) { + return 1; + } else { + return 0; + } + } + + /** + * Invokes any @BeforeResult annotated methods + * + * @see com.opensymphony.xwork2.interceptor.PreResultListener#beforeResult(com.opensymphony.xwork2.ActionInvocation,String) + */ + public void beforeResult(ActionInvocation invocation, String resultCode) { + Object action = invocation.getAction(); + List methods = new ArrayList(AnnotationUtils.getAnnotatedMethods(action.getClass(), BeforeResult.class)); + + if (methods.size() > 0) { + // methods are only sorted by priority + Collections.sort(methods, new Comparator() { + public int compare(Method method1, Method method2) { + return comparePriorities(method1.getAnnotation(BeforeResult.class).priority(), + method2.getAnnotation(BeforeResult.class).priority()); + } + }); + for (Method m : methods) { + try { + m.invoke(action, (Object[]) null); + } catch (Exception e) { + throw new XWorkException(e); + } + } + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Before.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Before.java new file mode 100644 index 000000000..cb0e55563 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Before.java @@ -0,0 +1,80 @@ +/* + * 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.interceptor.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * Marks a action method that needs to be executed before the main action method. + * + * + *

Annotation usage: + * + * + * The Before annotation can be applied at method level. + * + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
priorityno10Priority order of method execution
+ * + * + *

Example code: + * + *

+ * 
+ * public class SampleAction extends ActionSupport {
+ *
+ *  @Before
+ *  public void isAuthorized() throws AuthenticationException {
+ *    // authorize request, throw exception if failed
+ *  }
+ *
+ *  public String execute() {
+ *     // perform secure action
+ *     return SUCCESS;
+ *  }
+ * }
+ * 
+ * 
+ * + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Before { + int priority() default 10; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/BeforeResult.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/BeforeResult.java new file mode 100644 index 000000000..faeb4003c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/BeforeResult.java @@ -0,0 +1,80 @@ +/* + * 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.interceptor.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * Marks a action method that needs to be executed before the result. Return value is ignored. + * + * + *

Annotation usage: + * + * + * The BeforeResult annotation can be applied at method level. + * + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
priorityno10Priority order of method execution
+ * + * + *

Example code: + * + *

+ * 
+ * public class SampleAction extends ActionSupport {
+ *
+ *  @BeforeResult
+ *  public void isValid() throws ValidationException {
+ *    // validate model object, throw exception if failed
+ *  }
+ *
+ *  public String execute() {
+ *     // perform action
+ *     return SUCCESS;
+ *  }
+ * }
+ * 
+ * 
+ * + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BeforeResult { + int priority() default 10; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/BlockByDefault.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/BlockByDefault.java new file mode 100644 index 000000000..a87ac0b97 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/BlockByDefault.java @@ -0,0 +1,21 @@ +package com.opensymphony.xwork2.interceptor.annotations; + +import com.opensymphony.xwork2.Action; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares that by default fields on the {@link Action} class + * are NOT permitted to be set from HttpRequest parameters. + * To allow access to a field it must be annotated with {@link Allowed} + * + * @author martin.gilday + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface BlockByDefault { + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Blocked.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Blocked.java new file mode 100644 index 000000000..e9a288585 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/Blocked.java @@ -0,0 +1,18 @@ +package com.opensymphony.xwork2.interceptor.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares that the given field should NOT be able to be mutated through + * a HttpRequest parameter. + * + * @author martin.gilday + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Blocked { + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/InputConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/InputConfig.java new file mode 100644 index 000000000..5d70744d4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/InputConfig.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-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.interceptor.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.opensymphony.xwork2.Action; + +/** + * + * Marks a action method that if it's not validated by ValidationInterceptor then execute input method or input result. + * + * + *

Annotation usage: + * + * + * The InputConfig annotation can be applied at method level. + * + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
methodNamenoexecute this method if specific
resultNamenoreturn this result if methodName not specific
+ * + * + *

Example code: + * + *

+ * 
+ * public class SampleAction extends ActionSupport {
+ *
+ *  public void isValid() throws ValidationException {
+ *    // validate model object, throw exception if failed
+ *  }
+ *
+ *  @InputConfig(methodName="input")
+ *  public String execute() {
+ *     // perform action
+ *     return SUCCESS;
+ *  }
+ *  public String input() {
+ *     // perform some data filling
+ *     return INPUT;
+ *  }
+ * }
+ * 
+ * 
+ * + * @author zhouyanming, zhouyanming@gmail.com + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface InputConfig { + String methodName() default ""; + String resultName() default Action.INPUT; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/package.html new file mode 100644 index 000000000..8ac50a8b6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/annotations/package.html @@ -0,0 +1 @@ +Interceptor annotations. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/package.html new file mode 100644 index 000000000..505d814e0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/package.html @@ -0,0 +1 @@ +Interceptor classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockActionInvocation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockActionInvocation.java new file mode 100644 index 000000000..f137c80ee --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockActionInvocation.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.mock; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mock for an {@link ActionInvocation}. + * + * @author plightbo + * @author Rainer Hermanns + * @author tm_jee + * @version $Id$ + */ +public class MockActionInvocation implements ActionInvocation { + + private Object action; + private ActionContext invocationContext; + private ActionEventListener actionEventListener; + private ActionProxy proxy; + private Result result; + private String resultCode; + private ValueStack stack; + + private List preResultListeners = new ArrayList(); + + public Object getAction() { + return action; + } + + public void setAction(Object action) { + this.action = action; + } + + public ActionContext getInvocationContext() { + return invocationContext; + } + + public void setInvocationContext(ActionContext invocationContext) { + this.invocationContext = invocationContext; + } + + public ActionProxy getProxy() { + return proxy; + } + + public void setProxy(ActionProxy proxy) { + this.proxy = proxy; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + public String getResultCode() { + return resultCode; + } + + public void setResultCode(String resultCode) { + this.resultCode = resultCode; + } + + public ValueStack getStack() { + return stack; + } + + public void setStack(ValueStack stack) { + this.stack = stack; + } + + public boolean isExecuted() { + return false; + } + + public void addPreResultListener(PreResultListener listener) { + preResultListeners.add(listener); + } + + public String invoke() throws Exception { + for (Object preResultListener : preResultListeners) { + PreResultListener listener = (PreResultListener) preResultListener; + listener.beforeResult(this, resultCode); + } + return resultCode; + } + + public String invokeActionOnly() throws Exception { + return resultCode; + } + + public void setActionEventListener(ActionEventListener listener) { + this.actionEventListener = listener; + } + + public ActionEventListener getActionEventListener() { + return this.actionEventListener; + } + + public void init(ActionProxy proxy) { + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockActionProxy.java b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockActionProxy.java new file mode 100644 index 000000000..4eda2ef15 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockActionProxy.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.mock; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.entities.ActionConfig; + +/** + * Mock for an {@link ActionProxy}. + * + * @author Patrick Lightbody (plightbo at gmail dot com) + */ +public class MockActionProxy implements ActionProxy { + + Object action; + String actionName; + ActionConfig config; + boolean executeResult; + ActionInvocation invocation; + String namespace; + String method; + boolean executedCalled; + String returnedResult; + Configuration configuration; + + public void prepare() throws Exception {} + + public String execute() throws Exception { + executedCalled = true; + + return returnedResult; + } + + public void setReturnedResult(String returnedResult) { + this.returnedResult = returnedResult; + } + + public boolean isExecutedCalled() { + return executedCalled; + } + + public Object getAction() { + return action; + } + + public void setAction(Object action) { + this.action = action; + } + + public String getActionName() { + return actionName; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public ActionConfig getConfig() { + return config; + } + + public void setConfig(ActionConfig config) { + this.config = config; + } + + public boolean getExecuteResult() { + return executeResult; + } + + public void setExecuteResult(boolean executeResult) { + this.executeResult = executeResult; + } + + public ActionInvocation getInvocation() { + return invocation; + } + + public void setInvocation(ActionInvocation invocation) { + this.invocation = invocation; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockInterceptor.java new file mode 100644 index 000000000..c244770ed --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockInterceptor.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.mock; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.Interceptor; +import junit.framework.Assert; + + +/** + * Mock for an {@link com.opensymphony.xwork2.interceptor.Interceptor}. + * + * @author Jason Carreira + */ +public class MockInterceptor implements Interceptor { + + private static final long serialVersionUID = 2692551676567227756L; + + public static final String DEFAULT_FOO_VALUE = "fooDefault"; + + + private String expectedFoo = DEFAULT_FOO_VALUE; + private String foo = DEFAULT_FOO_VALUE; + private boolean executed = false; + + + public boolean isExecuted() { + return executed; + } + + public void setExpectedFoo(String expectedFoo) { + this.expectedFoo = expectedFoo; + } + + public String getExpectedFoo() { + return expectedFoo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + public String getFoo() { + return foo; + } + + /** + * Called to let an interceptor clean up any resources it has allocated. + */ + public void destroy() { + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof MockInterceptor)) { + return false; + } + + final MockInterceptor testInterceptor = (MockInterceptor) o; + + if (executed != testInterceptor.executed) { + return false; + } + + if ((expectedFoo != null) ? (!expectedFoo.equals(testInterceptor.expectedFoo)) : (testInterceptor.expectedFoo != null)) + { + return false; + } + + if ((foo != null) ? (!foo.equals(testInterceptor.foo)) : (testInterceptor.foo != null)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result; + result = ((expectedFoo != null) ? expectedFoo.hashCode() : 0); + result = (29 * result) + ((foo != null) ? foo.hashCode() : 0); + result = (29 * result) + (executed ? 1 : 0); + + return result; + } + + /** + * Called after an Interceptor is created, but before any requests are processed using the intercept() methodName. This + * gives the Interceptor a chance to initialize any needed resources. + */ + public void init() { + } + + /** + * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the + * request by the DefaultActionInvocation or to short-circuit the processing and just return a String return code. + */ + public String intercept(ActionInvocation invocation) throws Exception { + executed = true; + Assert.assertNotSame(DEFAULT_FOO_VALUE, foo); + Assert.assertEquals(expectedFoo, foo); + + return invocation.invoke(); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockObjectTypeDeterminer.java b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockObjectTypeDeterminer.java new file mode 100644 index 000000000..605aaf2c0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockObjectTypeDeterminer.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.mock; + +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import ognl.OgnlException; +import ognl.OgnlRuntime; + +import java.util.Map; + +/** + * Mocks the function of an ObjectTypeDeterminer for testing purposes. + * + * @author Gabe + */ +public class MockObjectTypeDeterminer implements ObjectTypeDeterminer { + + private Class keyClass; + private Class elementClass; + private String keyProperty; + private boolean shouldCreateIfNew; + + public MockObjectTypeDeterminer() {} + + + /** + * @param keyClass + * @param elementClass + * @param keyProperty + * @param shouldCreateIfNew + */ + public MockObjectTypeDeterminer(Class keyClass, Class elementClass, + String keyProperty, boolean shouldCreateIfNew) { + super(); + this.keyClass = keyClass; + this.elementClass = elementClass; + this.keyProperty = keyProperty; + this.shouldCreateIfNew = shouldCreateIfNew; + } + + public Class getKeyClass(Class parentClass, String property) { + return getKeyClass(); + } + + public Class getElementClass(Class parentClass, String property, Object key) { + return getElementClass(); + } + + public String getKeyProperty(Class parentClass, String property) { + return getKeyProperty(); + } + + public boolean shouldCreateIfNew(Class parentClass, String property, + Object target, String keyProperty, boolean isIndexAccessed) { + try { + System.out.println("ognl:"+OgnlRuntime.getPropertyAccessor(Map.class)+" this:"+this); + } catch (OgnlException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return isShouldCreateIfNew(); + } + + /** + * @return Returns the elementClass. + */ + public Class getElementClass() { + return elementClass; + } + /** + * @param elementClass The elementClass to set. + */ + public void setElementClass(Class elementClass) { + this.elementClass = elementClass; + } + /** + * @return Returns the keyClass. + */ + public Class getKeyClass() { + return keyClass; + } + /** + * @param keyClass The keyClass to set. + */ + public void setKeyClass(Class keyClass) { + this.keyClass = keyClass; + } + /** + * @return Returns the keyProperty. + */ + public String getKeyProperty() { + return keyProperty; + } + /** + * @param keyProperty The keyProperty to set. + */ + public void setKeyProperty(String keyProperty) { + this.keyProperty = keyProperty; + } + /** + * @return Returns the shouldCreateIfNew. + */ + public boolean isShouldCreateIfNew() { + return shouldCreateIfNew; + } + /** + * @param shouldCreateIfNew The shouldCreateIfNew to set. + */ + public void setShouldCreateIfNew(boolean shouldCreateIfNew) { + this.shouldCreateIfNew = shouldCreateIfNew; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java new file mode 100644 index 000000000..53eac6303 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.mock; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.Result; + +/** + * Mock for a {@link Result}. + * + * @author Mike + * @author Rainer Hermanns + */ +public class MockResult implements Result { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof MockResult)) { + return false; + } + + return true; + } + + public void execute(ActionInvocation invocation) throws Exception { + // no op + } + + @Override + public int hashCode() { + return 10; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/mock/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/package.html new file mode 100644 index 000000000..61bdf48e9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/mock/package.html @@ -0,0 +1 @@ +XWork specific mock classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/ObjectProxy.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/ObjectProxy.java new file mode 100644 index 000000000..b01fd6736 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/ObjectProxy.java @@ -0,0 +1,55 @@ +/* + * 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.ognl; + +/** + * An Object to use within OGNL to proxy other Objects + * usually Collections that you set in a different place + * on the ValueStack but want to retain the context information + * about where they previously were. + * + * @author Gabe + */ +public class ObjectProxy { + private Object value; + private Class lastClassAccessed; + private String lastPropertyAccessed; + + public Class getLastClassAccessed() { + return lastClassAccessed; + } + + public void setLastClassAccessed(Class lastClassAccessed) { + this.lastClassAccessed = lastClassAccessed; + } + + public String getLastPropertyAccessed() { + return lastPropertyAccessed; + } + + public void setLastPropertyAccessed(String lastPropertyAccessed) { + this.lastPropertyAccessed = lastPropertyAccessed; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlNullHandlerWrapper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlNullHandlerWrapper.java new file mode 100644 index 000000000..95a0e35a2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlNullHandlerWrapper.java @@ -0,0 +1,24 @@ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.conversion.NullHandler; + +import java.util.Map; + +public class OgnlNullHandlerWrapper implements ognl.NullHandler { + + private NullHandler wrapped; + + public OgnlNullHandlerWrapper(NullHandler target) { + this.wrapped = target; + } + + public Object nullMethodResult(Map context, Object target, + String methodName, Object[] args) { + return wrapped.nullMethodResult(context, target, methodName, args); + } + + public Object nullPropertyValue(Map context, Object target, Object property) { + return wrapped.nullPropertyValue(context, target, property); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlReflectionContextFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlReflectionContextFactory.java new file mode 100644 index 000000000..03a5537f1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlReflectionContextFactory.java @@ -0,0 +1,14 @@ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.util.reflection.ReflectionContextFactory; +import ognl.Ognl; + +import java.util.Map; + +public class OgnlReflectionContextFactory implements ReflectionContextFactory { + + public Map createDefaultContext(Object root) { + return Ognl.createDefaultContext(root); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlReflectionProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlReflectionProvider.java new file mode 100644 index 000000000..b59720e61 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlReflectionProvider.java @@ -0,0 +1,126 @@ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; +import ognl.Ognl; +import ognl.OgnlException; +import ognl.OgnlRuntime; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +public class OgnlReflectionProvider implements ReflectionProvider { + + private OgnlUtil ognlUtil; + + @Inject + public void setOgnlUtil(OgnlUtil ognlUtil) { + this.ognlUtil = ognlUtil; + } + + public Field getField(Class inClass, String name) { + return OgnlRuntime.getField(inClass, name); + } + + public Method getGetMethod(Class targetClass, String propertyName) + throws IntrospectionException, ReflectionException { + try { + return OgnlRuntime.getGetMethod(null, targetClass, propertyName); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public Method getSetMethod(Class targetClass, String propertyName) + throws IntrospectionException, ReflectionException { + try { + return OgnlRuntime.getSetMethod(null, targetClass, propertyName); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public void setProperties(Map props, Object o, Map context) { + ognlUtil.setProperties(props, o, context); + } + + public void setProperties(Map props, Object o, Map context, + boolean throwPropertyExceptions) throws ReflectionException{ + ognlUtil.setProperties(props, o, context, throwPropertyExceptions); + + } + + public void setProperties(Map properties, Object o) { + ognlUtil.setProperties(properties, o); + } + + public PropertyDescriptor getPropertyDescriptor(Class targetClass, + String propertyName) throws IntrospectionException, + ReflectionException { + try { + return OgnlRuntime.getPropertyDescriptor(targetClass, propertyName); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public void copy(Object from, Object to, Map context, + Collection exclusions, Collection inclusions) { + ognlUtil.copy(from, to, context, exclusions, inclusions); + } + + public Object getRealTarget(String property, Map context, Object root) + throws ReflectionException { + try { + return ognlUtil.getRealTarget(property, context, root); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public void setProperty(String name, Object value, Object o, Map context) { + ognlUtil.setProperty(name, value, o, context); + } + + public void setProperty(String name, Object value, Object o, Map context, boolean throwPropertyExceptions) { + ognlUtil.setProperty(name, value, o, context, throwPropertyExceptions); + } + + public Map getBeanMap(Object source) throws IntrospectionException, + ReflectionException { + try { + return ognlUtil.getBeanMap(source); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public Object getValue(String expression, Map context, Object root) + throws ReflectionException { + try { + return ognlUtil.getValue(expression, context, root); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public void setValue(String expression, Map context, Object root, + Object value) throws ReflectionException { + try { + Ognl.setValue(expression, context, root, value); + } catch (OgnlException e) { + throw new ReflectionException(e); + } + } + + public PropertyDescriptor[] getPropertyDescriptors(Object source) + throws IntrospectionException { + return ognlUtil.getPropertyDescriptors(source); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java new file mode 100644 index 000000000..55a71be31 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.conversion.TypeConverter; + +import java.lang.reflect.Member; +import java.util.Map; + +/** + * Wraps an XWork type conversion class for as an OGNL TypeConverter + */ +public class OgnlTypeConverterWrapper implements ognl.TypeConverter { + + private TypeConverter typeConverter; + + public OgnlTypeConverterWrapper(TypeConverter conv) { + if (conv == null) { + throw new IllegalArgumentException("Wrapped type converter cannot be null"); + } + this.typeConverter = conv; + } + + public Object convertValue(Map context, Object target, Member member, + String propertyName, Object value, Class toType) { + return typeConverter.convertValue(context, target, member, propertyName, value, toType); + } + + public TypeConverter getTarget() { + return typeConverter; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java new file mode 100644 index 000000000..1ccf7cf0a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java @@ -0,0 +1,432 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import ognl.*; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * Utility class that provides common access to the Ognl APIs for + * setting and getting properties from objects (usually Actions). + * + * @author Jason Carreira + */ +public class OgnlUtil { + + private static final Logger LOG = LoggerFactory.getLogger(OgnlUtil.class); + private ConcurrentHashMap expressions = new ConcurrentHashMap(); + private final ConcurrentHashMap beanInfoCache = new ConcurrentHashMap(); + + private TypeConverter defaultConverter; + static boolean devMode = false; + static boolean enableExpressionCache = true; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.defaultConverter = new OgnlTypeConverterWrapper(conv); + } + + @Inject("devMode") + public static void setDevMode(String mode) { + devMode = "true".equals(mode); + } + + @Inject("enableOGNLExpressionCache") + public static void setEnableExpressionCache(String cache) { + enableExpressionCache = "true".equals(cache); + } + + /** + * Sets the object's properties using the default type converter, defaulting to not throw + * exceptions for problems setting the properties. + * + * @param props the properties being set + * @param o the object + * @param context the action context + */ + public void setProperties(Map props, Object o, Map context) { + setProperties(props, o, context, false); + } + + /** + * Sets the object's properties using the default type converter. + * + * @param props the properties being set + * @param o the object + * @param context the action context + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the properties + */ + public void setProperties(Map props, Object o, Map context, boolean throwPropertyExceptions) throws ReflectionException{ + if (props == null) { + return; + } + + Ognl.setTypeConverter(context, getTypeConverterFromContext(context)); + + Object oldRoot = Ognl.getRoot(context); + Ognl.setRoot(context, o); + + for (Map.Entry entry : props.entrySet()) { + String expression = entry.getKey(); + internalSetProperty(expression, entry.getValue(), o, context, throwPropertyExceptions); + } + + Ognl.setRoot(context, oldRoot); + } + + /** + * Sets the properties on the object using the default context, defaulting to not throwing + * exceptions for problems setting the properties. + * + * @param properties + * @param o + */ + public void setProperties(Map properties, Object o) { + setProperties(properties, o, false); + } + + /** + * Sets the properties on the object using the default context. + * + * @param properties the property map to set on the object + * @param o the object to set the properties into + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the properties + */ + public void setProperties(Map properties, Object o, boolean throwPropertyExceptions) { + Map context = Ognl.createDefaultContext(o); + setProperties(properties, o, context, throwPropertyExceptions); + } + + /** + * Sets the named property to the supplied value on the Object, defaults to not throwing + * property exceptions. + * + * @param name the name of the property to be set + * @param value the value to set into the named property + * @param o the object upon which to set the property + * @param context the context which may include the TypeConverter + */ + public void setProperty(String name, Object value, Object o, Map context) { + setProperty(name, value, o, context, false); + } + + /** + * Sets the named property to the supplied value on the Object. + * + * @param name the name of the property to be set + * @param value the value to set into the named property + * @param o the object upon which to set the property + * @param context the context which may include the TypeConverter + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the property + */ + public void setProperty(String name, Object value, Object o, Map context, boolean throwPropertyExceptions) { + Ognl.setTypeConverter(context, getTypeConverterFromContext(context)); + + Object oldRoot = Ognl.getRoot(context); + Ognl.setRoot(context, o); + + internalSetProperty(name, value, o, context, throwPropertyExceptions); + + Ognl.setRoot(context, oldRoot); + } + + /** + * Looks for the real target with the specified property given a root Object which may be a + * CompoundRoot. + * + * @return the real target or null if no object can be found with the specified property + */ + public Object getRealTarget(String property, Map context, Object root) throws OgnlException { + //special keyword, they must be cutting the stack + if ("top".equals(property)) { + return root; + } + + if (root instanceof CompoundRoot) { + // find real target + CompoundRoot cr = (CompoundRoot) root; + + try { + for (Object target : cr) { + if ( + OgnlRuntime.hasSetProperty((OgnlContext) context, target, property) + || + OgnlRuntime.hasGetProperty((OgnlContext) context, target, property) + || + OgnlRuntime.getIndexedPropertyType((OgnlContext) context, target.getClass(), property) != OgnlRuntime.INDEXED_PROPERTY_NONE + ) { + return target; + } + } + } catch (IntrospectionException ex) { + throw new ReflectionException("Cannot figure out real target class", ex); + } + + return null; + } + + return root; + } + + + /** + * Wrapper around Ognl.setValue() to handle type conversion for collection elements. + * Ideally, this should be handled by OGNL directly. + */ + public void setValue(String name, Map context, Object root, Object value) throws OgnlException { + Ognl.setValue(compile(name), context, root, value); + } + + public Object getValue(String name, Map context, Object root) throws OgnlException { + return Ognl.getValue(compile(name), context, root); + } + + public Object getValue(String name, Map context, Object root, Class resultType) throws OgnlException { + return Ognl.getValue(compile(name), context, root, resultType); + } + + + public Object compile(String expression) throws OgnlException { + if (enableExpressionCache) { + Object o = expressions.get(expression); + if (o == null) { + o = Ognl.parseExpression(expression); + expressions.put(expression, o); + } + return o; + } else + return Ognl.parseExpression(expression); + } + + /** + * Copies the properties in the object "from" and sets them in the object "to" + * using specified type converter, or {@link com.opensymphony.xwork2.conversion.impl.XWorkConverter} if none + * is specified. + * + * @param from the source object + * @param to the target object + * @param context the action context we're running under + * @param exclusions collection of method names to excluded from copying ( can be null) + * @param inclusions collection of method names to included copying (can be null) + * note if exclusions AND inclusions are supplied and not null nothing will get copied. + */ + public void copy(Object from, Object to, Map context, Collection exclusions, Collection inclusions) { + if (from == null || to == null) { + LOG.warn("Attempting to copy from or to a null source. This is illegal and is bein skipped. This may be due to an error in an OGNL expression, action chaining, or some other event."); + + return; + } + + TypeConverter conv = getTypeConverterFromContext(context); + Map contextFrom = Ognl.createDefaultContext(from); + Ognl.setTypeConverter(contextFrom, conv); + Map contextTo = Ognl.createDefaultContext(to); + Ognl.setTypeConverter(contextTo, conv); + + PropertyDescriptor[] fromPds; + PropertyDescriptor[] toPds; + + try { + fromPds = getPropertyDescriptors(from); + toPds = getPropertyDescriptors(to); + } catch (IntrospectionException e) { + LOG.error("An error occured", e); + + return; + } + + Map toPdHash = new HashMap(); + + for (PropertyDescriptor toPd : toPds) { + toPdHash.put(toPd.getName(), toPd); + } + + for (PropertyDescriptor fromPd : fromPds) { + if (fromPd.getReadMethod() != null) { + boolean copy = true; + if (exclusions != null && exclusions.contains(fromPd.getName())) { + copy = false; + } else if (inclusions != null && !inclusions.contains(fromPd.getName())) { + copy = false; + } + + if (copy == true) { + PropertyDescriptor toPd = toPdHash.get(fromPd.getName()); + if ((toPd != null) && (toPd.getWriteMethod() != null)) { + try { + Object expr = compile(fromPd.getName()); + Object value = Ognl.getValue(expr, contextFrom, from); + Ognl.setValue(expr, contextTo, to, value); + } catch (OgnlException e) { + // ignore, this is OK + } + } + + } + + } + + } + } + + + /** + * Copies the properties in the object "from" and sets them in the object "to" + * using specified type converter, or {@link com.opensymphony.xwork2.conversion.impl.XWorkConverter} if none + * is specified. + * + * @param from the source object + * @param to the target object + * @param context the action context we're running under + */ + public void copy(Object from, Object to, Map context) { + copy(from, to, context, null, null); + } + + /** + * Get's the java beans property descriptors for the given source. + * + * @param source the source object. + * @return property descriptors. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public PropertyDescriptor[] getPropertyDescriptors(Object source) throws IntrospectionException { + BeanInfo beanInfo = getBeanInfo(source); + return beanInfo.getPropertyDescriptors(); + } + + + /** + * Get's the java beans property descriptors for the given class. + * + * @param clazz the source object. + * @return property descriptors. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws IntrospectionException { + BeanInfo beanInfo = getBeanInfo(clazz); + return beanInfo.getPropertyDescriptors(); + } + + /** + * Creates a Map with read properties for the given source object. + *

+ * If the source object does not have a read property (i.e. write-only) then + * the property is added to the map with the value here is no read method for property-name. + * + * @param source the source object. + * @return a Map with (key = read property name, value = value of read property). + * @throws IntrospectionException is thrown if an exception occurs during introspection. + * @throws OgnlException is thrown by OGNL if the property value could not be retrieved + */ + public Map getBeanMap(Object source) throws IntrospectionException, OgnlException { + Map beanMap = new HashMap(); + Map sourceMap = Ognl.createDefaultContext(source); + PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(source); + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + String propertyName = propertyDescriptor.getDisplayName(); + Method readMethod = propertyDescriptor.getReadMethod(); + if (readMethod != null) { + Object expr = compile(propertyName); + Object value = Ognl.getValue(expr, sourceMap, source); + beanMap.put(propertyName, value); + } else { + beanMap.put(propertyName, "There is no read method for " + propertyName); + } + } + return beanMap; + } + + /** + * Get's the java bean info for the given source object. Calls getBeanInfo(Class c). + * + * @param from the source object. + * @return java bean info. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public BeanInfo getBeanInfo(Object from) throws IntrospectionException { + return getBeanInfo(from.getClass()); + } + + + /** + * Get's the java bean info for the given source. + * + * @param clazz the source class. + * @return java bean info. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public BeanInfo getBeanInfo(Class clazz) throws IntrospectionException { + synchronized (beanInfoCache) { + BeanInfo beanInfo; + beanInfo = beanInfoCache.get(clazz); + if (beanInfo == null) { + beanInfo = Introspector.getBeanInfo(clazz, Object.class); + beanInfoCache.put(clazz, beanInfo); + } + return beanInfo; + } + } + + void internalSetProperty(String name, Object value, Object o, Map context, boolean throwPropertyExceptions) throws ReflectionException{ + try { + setValue(name, context, o, value); + } catch (OgnlException e) { + Throwable reason = e.getReason(); + String msg = "Caught OgnlException while setting property '" + name + "' on type '" + o.getClass().getName() + "'."; + Throwable exception = (reason == null) ? e : reason; + + if (throwPropertyExceptions) { + throw new ReflectionException(msg, exception); + } else { + if (devMode) { + LOG.warn(msg, exception); + } + } + } + } + + TypeConverter getTypeConverterFromContext(Map context) { + /*ValueStack stack = (ValueStack) context.get(ActionContext.VALUE_STACK); + Container cont = (Container)stack.getContext().get(ActionContext.CONTAINER); + if (cont != null) { + return new OgnlTypeConverterWrapper(cont.getInstance(XWorkConverter.class)); + } else { + throw new IllegalArgumentException("Cannot find type converter in context map"); + } + */ + return defaultConverter; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java new file mode 100644 index 000000000..8d1a5684b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java @@ -0,0 +1,491 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.MemberAccessValueStack; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.logging.LoggerUtils; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.*; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Ognl implementation of a value stack that allows for dynamic Ognl expressions to be evaluated against it. When + * evaluating an expression, the stack will be searched down the stack, from the latest objects pushed in to the + * earliest, looking for a bean with a getter or setter for the given property or a method of the given name (depending + * on the expression being evaluated). + * + * @author Patrick Lightbody + * @author tm_jee + * @version $Date$ $Id$ + */ +public class OgnlValueStack implements Serializable, ValueStack, ClearableValueStack, MemberAccessValueStack { + + private static final long serialVersionUID = 370737852934925530L; + + private static Logger LOG = LoggerFactory.getLogger(OgnlValueStack.class); + private boolean devMode; + private boolean logMissingProperties; + public static final String THROW_EXCEPTION_ON_FAILURE = OgnlValueStack.class.getName() + ".throwExceptionOnFailure"; + + CompoundRoot root; + transient Map context; + Class defaultType; + Map overrides; + transient OgnlUtil ognlUtil; + + transient SecurityMemberAccess securityMemberAccess; + + protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) { + setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess); + push(prov); + } + + + protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticAccess) { + setRoot(xworkConverter, accessor, new CompoundRoot(vs.getRoot()), allowStaticAccess); + } + + @Inject + public void setOgnlUtil(OgnlUtil ognlUtil) { + this.ognlUtil = ognlUtil; + } + + protected void setRoot(XWorkConverter xworkConverter, + CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticMethodAccess) { + this.root = compoundRoot; + this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess); + this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), + securityMemberAccess); + context.put(VALUE_STACK, this); + Ognl.setClassResolver(context, accessor); + ((OgnlContext) context).setTraceEvaluations(false); + ((OgnlContext) context).setKeepLastEvaluation(false); + } + + @Inject("devMode") + public void setDevMode(String mode) { + devMode = "true".equalsIgnoreCase(mode); + } + + @Inject(value = "logMissingProperties", required = false ) + public void setLogMissingProperties(String logMissingProperties) { + this.logMissingProperties = "true".equalsIgnoreCase(logMissingProperties); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#getContext() + */ + public Map getContext() { + return context; + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#setDefaultType(java.lang.Class) + */ + public void setDefaultType(Class defaultType) { + this.defaultType = defaultType; + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#setExprOverrides(java.util.Map) + */ + public void setExprOverrides(Map overrides) { + if (this.overrides == null) { + this.overrides = overrides; + } else { + this.overrides.putAll(overrides); + } + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#getExprOverrides() + */ + public Map getExprOverrides() { + return this.overrides; + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#getRoot() + */ + public CompoundRoot getRoot() { + return root; + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object) + */ + public void setValue(String expr, Object value) { + setValue(expr, value, devMode); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object, boolean) + */ + public void setValue(String expr, Object value, boolean throwExceptionOnFailure) { + Map context = getContext(); + + try { + context.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, expr); + context.put(REPORT_ERRORS_ON_NO_PROP, (throwExceptionOnFailure) ? Boolean.TRUE : Boolean.FALSE); + ognlUtil.setValue(expr, context, root, value); + } catch (OgnlException e) { + String msg = "Error setting expression '" + expr + "' with value '" + value + "'"; + if (LOG.isWarnEnabled()) { + LOG.warn(msg, e); + } + if (throwExceptionOnFailure) { + throw new XWorkException(msg, e); + } + } catch (RuntimeException re) { //XW-281 + if (throwExceptionOnFailure) { + StringBuilder msg = new StringBuilder(); + msg.append("Error setting expression '"); + msg.append(expr); + msg.append("' with value "); + + if (value instanceof Object[]) { + Object[] valueArray = (Object[]) value; + msg.append("["); + for (int index = 0; index < valueArray.length; index++) { + msg.append("'"); + msg.append(valueArray[index]); + msg.append("'"); + + if (index < (valueArray.length + 1)) + msg.append(", "); + } + msg.append("]"); + } else { + msg.append("'"); + msg.append(value); + msg.append("'"); + } + + throw new XWorkException(msg.toString(), re); + } else { + if (LOG.isWarnEnabled()) { + LOG.warn("Error setting value", re); + } + } + } finally { + ReflectionContextState.clear(context); + context.remove(XWorkConverter.CONVERSION_PROPERTY_FULLNAME); + context.remove(REPORT_ERRORS_ON_NO_PROP); + } + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#findString(java.lang.String) + */ + public String findString(String expr) { + return (String) findValue(expr, String.class); + } + + public String findString(String expr, boolean throwExceptionOnFailure) { + return (String) findValue(expr, String.class, throwExceptionOnFailure); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String) + */ + public Object findValue(String expr, boolean throwExceptionOnFailure) { + try { + if (expr == null) { + return null; + } + + if ((overrides != null) && overrides.containsKey(expr)) { + expr = (String) overrides.get(expr); + } + + if (defaultType != null) { + return findValue(expr, defaultType); + } + + Object value = null; + try { + if (throwExceptionOnFailure) + context.put(THROW_EXCEPTION_ON_FAILURE, true); + value = ognlUtil.getValue(expr, context, root); + } finally { + context.remove(THROW_EXCEPTION_ON_FAILURE); + } + + if (value != null) { + return value; + } else { + return findInContext(expr); + } + } catch (OgnlException e) { + Object ret = findInContext(expr); + + if (ret != null) + return ret; + else { + if (e instanceof NoSuchPropertyException && devMode && logMissingProperties) + LOG.warn("Could not find property [" + ((NoSuchPropertyException)e).getName() + "]"); + + if (throwExceptionOnFailure) + throw new XWorkException(e); + else + return null; + } + } catch (Exception e) { + logLookupFailure(expr, e); + + if (throwExceptionOnFailure) + throw new XWorkException(e); + + return findInContext(expr); + } finally { + ReflectionContextState.clear(context); + } + } + + public Object findValue(String expr) { + return findValue(expr, false); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String, java.lang.Class) + */ + public Object findValue(String expr, Class asType, boolean throwExceptionOnFailure) { + try { + if (expr == null) { + return null; + } + + if ((overrides != null) && overrides.containsKey(expr)) { + expr = (String) overrides.get(expr); + } + + Object value = null; + try { + if (throwExceptionOnFailure) + context.put(THROW_EXCEPTION_ON_FAILURE, true); + value = ognlUtil.getValue(expr, context, root, asType); + } finally { + context.remove(THROW_EXCEPTION_ON_FAILURE); + } + + if (value != null) { + return value; + } else { + return findInContext(expr); + } + } catch (OgnlException e) { + Object ret = findInContext(expr); + + if (ret != null) + return ret; + else { + if (e instanceof NoSuchPropertyException && devMode && logMissingProperties) + LOG.warn("Could not find property [" + ((NoSuchPropertyException)e).getName() + "]"); + + if (throwExceptionOnFailure) + throw new XWorkException(e); + else + return null; + } + } catch (Exception e) { + logLookupFailure(expr, e); + + if (throwExceptionOnFailure) + throw new XWorkException(e); + + return findInContext(expr); + } finally { + ReflectionContextState.clear(context); + } + } + + private Object findInContext(String name) { + return getContext().get(name); + } + + public Object findValue(String expr, Class asType) { + return findValue(expr, asType, false); + } + + /** + * Look for available properties on an existing class. + * + * @param c the class to search on + * @param expr the property expression + * @param availableProperties a set of properties found + * @param parent a parent property + * @throws IntrospectionException when Ognl can't get property descriptors + */ + private void findAvailableProperties(Class c, String expr, Set availableProperties, String parent) throws IntrospectionException { + PropertyDescriptor[] descriptors = ognlUtil.getPropertyDescriptors(c); + for (PropertyDescriptor pd : descriptors) { + String name = pd.getDisplayName(); + if (parent != null && expr.contains(".")) { + name = expr.substring(0, expr.indexOf(".") + 1) + name; + } + if (expr.startsWith(name)) { + availableProperties.add((parent != null) ? parent + "." + name : name); + if (expr.equals(name)) break; // no need to go any further + if (expr.contains(".")) { + String property = expr.substring(expr.indexOf(".") + 1); + // if there is a nested property (indicated by a dot), chop it off so we can look for method name + String rawProperty = (property.contains(".")) ? property.substring(0, property.indexOf(".")) : property; + String methodToLookFor = "get" + rawProperty.substring(0, 1).toUpperCase() + rawProperty.substring(1); + Method[] methods = pd.getPropertyType().getMethods(); + for (Method method : methods) { + if (method.getName().equals(methodToLookFor)) { + availableProperties.add(name + "." + rawProperty); + Class returnType = method.getReturnType(); + findAvailableProperties(returnType, property, availableProperties, name); + } + } + + } + } + } + } + + /** + * Log a failed lookup, being more verbose when devMode=true. + * + * @param expr The failed expression + * @param e The thrown exception. + */ + private void logLookupFailure(String expr, Exception e) { + String msg = LoggerUtils.format("Caught an exception while evaluating expression '#0' against value stack", expr); + if (devMode && LOG.isWarnEnabled()) { + LOG.warn(msg, e); + LOG.warn("NOTE: Previous warning message was issued due to devMode set to true."); + } else if (LOG.isDebugEnabled()) { + LOG.debug(msg, e); + } + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#peek() + */ + public Object peek() { + return root.peek(); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#pop() + */ + public Object pop() { + return root.pop(); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#push(java.lang.Object) + */ + public void push(Object o) { + root.push(o); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#set(java.lang.String, java.lang.Object) + */ + public void set(String key, Object o) { + //set basically is backed by a Map + //pushed on the stack with a key + //being put on the map and the + //Object being the value + + Map setMap = null; + + //check if this is a Map + //put on the stack for setting + //if so just use the old map (reduces waste) + Object topObj = peek(); + if (topObj instanceof Map + && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null) { + + setMap = (Map) topObj; + } else { + setMap = new HashMap(); + //the map identifier key ensures + //that this map was put there + //for set purposes and not by a user + //whose data we don't want to touch + setMap.put(MAP_IDENTIFIER_KEY, ""); + push(setMap); + } + setMap.put(key, o); + + } + + + private static final String MAP_IDENTIFIER_KEY = "com.opensymphony.xwork2.util.OgnlValueStack.MAP_IDENTIFIER_KEY"; + + /* (non-Javadoc) + * @see com.opensymphony.xwork2.util.ValueStack#size() + */ + public int size() { + return root.size(); + } + + private Object readResolve() { + // TODO: this should be done better + ActionContext ac = ActionContext.getContext(); + Container cont = ac.getContainer(); + XWorkConverter xworkConverter = cont.getInstance(XWorkConverter.class); + CompoundRootAccessor accessor = (CompoundRootAccessor) cont.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()); + TextProvider prov = cont.getInstance(TextProvider.class, "system"); + boolean allow = "true".equals(cont.getInstance(String.class, "allowStaticMethodAccess")); + OgnlValueStack aStack = new OgnlValueStack(xworkConverter, accessor, prov, allow); + aStack.setOgnlUtil(cont.getInstance(OgnlUtil.class)); + aStack.setRoot(xworkConverter, accessor, this.root, allow); + + return aStack; + } + + + public void clearContextValues() { + //this is an OGNL ValueStack so the context will be an OgnlContext + //it would be better to make context of type OgnlContext + ((OgnlContext)context).getValues().clear(); + } + + public void setAcceptProperties(Set acceptedProperties) { + securityMemberAccess.setAcceptProperties(acceptedProperties); + } + + public void setExcludeProperties(Set excludeProperties) { + securityMemberAccess.setExcludeProperties(excludeProperties); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java new file mode 100644 index 000000000..ad16587d6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java @@ -0,0 +1,117 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.conversion.NullHandler; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import ognl.MethodAccessor; +import ognl.OgnlRuntime; +import ognl.PropertyAccessor; + +import java.util.Map; +import java.util.Set; + +/** + * Creates an Ognl value stack + */ +public class OgnlValueStackFactory implements ValueStackFactory { + + private XWorkConverter xworkConverter; + private CompoundRootAccessor compoundRootAccessor; + private TextProvider textProvider; + private Container container; + private boolean allowStaticMethodAccess; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject("system") + public void setTextProvider(TextProvider textProvider) { + this.textProvider = textProvider; + } + + @Inject(value="allowStaticMethodAccess", required=false) + public void setAllowStaticMethodAccess(String allowStaticMethodAccess) { + this.allowStaticMethodAccess = "true".equalsIgnoreCase(allowStaticMethodAccess); + } + + public ValueStack createValueStack() { + ValueStack stack = new OgnlValueStack(xworkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess); + container.inject(stack); + stack.getContext().put(ActionContext.CONTAINER, container); + return stack; + } + + public ValueStack createValueStack(ValueStack stack) { + ValueStack result = new OgnlValueStack(stack, xworkConverter, compoundRootAccessor, allowStaticMethodAccess); + container.inject(result); + stack.getContext().put(ActionContext.CONTAINER, container); + return result; + } + + @Inject + public void setContainer(Container container) throws ClassNotFoundException { + Set names = container.getInstanceNames(PropertyAccessor.class); + if (names != null) { + for (String name : names) { + Class cls = Class.forName(name); + if (cls != null) { + if (Map.class.isAssignableFrom(cls)) { + PropertyAccessor acc = container.getInstance(PropertyAccessor.class, name); + } + OgnlRuntime.setPropertyAccessor(cls, container.getInstance(PropertyAccessor.class, name)); + if (compoundRootAccessor == null && CompoundRoot.class.isAssignableFrom(cls)) { + compoundRootAccessor = (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, name); + } + } + } + } + + names = container.getInstanceNames(MethodAccessor.class); + if (names != null) { + for (String name : names) { + Class cls = Class.forName(name); + if (cls != null) { + OgnlRuntime.setMethodAccessor(cls, container.getInstance(MethodAccessor.class, name)); + } + } + } + + names = container.getInstanceNames(NullHandler.class); + if (names != null) { + for (String name : names) { + Class cls = Class.forName(name); + if (cls != null) { + OgnlRuntime.setNullHandler(cls, new OgnlNullHandlerWrapper(container.getInstance(NullHandler.class, name))); + } + } + } + if (compoundRootAccessor == null) { + throw new IllegalStateException("Couldn't find the compound root accessor"); + } + this.container = container; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java new file mode 100644 index 000000000..0a5069f06 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java @@ -0,0 +1,128 @@ +/* + * 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.ognl; + +import ognl.DefaultMemberAccess; + +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Allows access decisions to be made on the basis of whether a member is static or not. + * Also blocks or allows access to properties. + */ +public class SecurityMemberAccess extends DefaultMemberAccess { + + private boolean allowStaticMethodAccess; + Set excludeProperties = Collections.emptySet(); + Set acceptProperties = Collections.emptySet(); + + public SecurityMemberAccess(boolean method) { + super(false); + allowStaticMethodAccess = method; + } + + public boolean getAllowStaticMethodAccess() { + return allowStaticMethodAccess; + } + + public void setAllowStaticMethodAccess(boolean allowStaticMethodAccess) { + this.allowStaticMethodAccess = allowStaticMethodAccess; + } + + @Override + public boolean isAccessible(Map context, Object target, Member member, + String propertyName) { + + boolean allow = true; + int modifiers = member.getModifiers(); + if (Modifier.isStatic(modifiers)) { + if (member instanceof Method && !getAllowStaticMethodAccess()) { + allow = false; + if (target instanceof Class) { + Class clazz = (Class) target; + Method method = (Method) member; + if (Enum.class.isAssignableFrom(clazz) && method.getName().equals("values")) + allow = true; + } + } + } + + //failed static test + if (!allow) + return false; + + // Now check for standard scope rules + if (!super.isAccessible(context, target, member, propertyName)) + return false; + + return isAcceptableProperty(propertyName); + } + + protected boolean isAcceptableProperty(String name) { + if ( name == null) { + return true; + } + + if (isAccepted(name) && !isExcluded(name)) { + return true; + } + return false; + } + + protected boolean isAccepted(String paramName) { + if (!this.acceptProperties.isEmpty()) { + for (Pattern pattern : acceptProperties) { + Matcher matcher = pattern.matcher(paramName); + if (matcher.matches()) { + return true; + } + } + + //no match, but acceptedParams is not empty + return false; + } + + //empty acceptedParams + return true; + } + + protected boolean isExcluded(String paramName) { + if (!this.excludeProperties.isEmpty()) { + for (Pattern pattern : excludeProperties) { + Matcher matcher = pattern.matcher(paramName); + if (matcher.matches()) { + return true; + } + } + } + return false; + } + + public void setExcludeProperties(Set excludeProperties) { + this.excludeProperties = excludeProperties; + } + + public void setAcceptProperties(Set acceptedProperties) { + this.acceptProperties = acceptedProperties; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java new file mode 100644 index 000000000..dbf21f67b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java @@ -0,0 +1,38 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.conversion.TypeConverter; + +import java.lang.reflect.Member; +import java.util.Map; + +/** + * Wraps an OGNL TypeConverter as an XWork TypeConverter + */ +public class XWorkTypeConverterWrapper implements TypeConverter { + + private ognl.TypeConverter typeConverter; + + public XWorkTypeConverterWrapper(ognl.TypeConverter conv) { + this.typeConverter = conv; + } + + public Object convertValue(Map context, Object target, Member member, + String propertyName, Object value, Class toType) { + return typeConverter.convertValue(context, target, member, propertyName, value, toType); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java new file mode 100644 index 000000000..aebfdb6a9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java @@ -0,0 +1,326 @@ +/* + * 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.ognl.accessor; + +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import ognl.*; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.util.*; + + +/** + * A stack that is able to call methods on objects in the stack. + * + * @author $Author$ + * @author Rainer Hermanns + * @version $Revision$ + */ +public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, ClassResolver { + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceAccessor(OgnlContext context, Object target, Object index) { + return null; + } + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceSetter(OgnlContext context, Object target, Object index) { + return null; + } + + private final static Logger LOG = LoggerFactory.getLogger(CompoundRootAccessor.class); + private final static Class[] EMPTY_CLASS_ARRAY = new Class[0]; + private static Map invalidMethods = new HashMap(); + + static boolean devMode = false; + + @Inject("devMode") + public static void setDevMode(String mode) { + devMode = "true".equals(mode); + } + + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + CompoundRoot root = (CompoundRoot) target; + OgnlContext ognlContext = (OgnlContext) context; + + for (Object o : root) { + if (o == null) { + continue; + } + + try { + if (OgnlRuntime.hasSetProperty(ognlContext, o, name)) { + OgnlRuntime.setProperty(ognlContext, o, name, value); + + return; + } else if (o instanceof Map) { + Map map = (Map) o; + try { + map.put(name, value); + return; + } catch (UnsupportedOperationException e) { + // This is an unmodifiable Map, so move on to the next element in the stack + } + } +// } catch (OgnlException e) { +// if (e.getReason() != null) { +// final String msg = "Caught an Ognl exception while setting property " + name; +// log.error(msg, e); +// throw new RuntimeException(msg, e.getReason()); +// } + } catch (IntrospectionException e) { + // this is OK if this happens, we'll just keep trying the next + } + } + + Boolean reportError = (Boolean) context.get(ValueStack.REPORT_ERRORS_ON_NO_PROP); + + final String msg = "No object in the CompoundRoot has a publicly accessible property named '" + name + "' (no setter could be found)."; + + if ((reportError != null) && (reportError.booleanValue())) { + throw new XWorkException(msg); + } else { + if (devMode) { + LOG.warn(msg); + } + } + } + + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + CompoundRoot root = (CompoundRoot) target; + OgnlContext ognlContext = (OgnlContext) context; + + if (name instanceof Integer) { + Integer index = (Integer) name; + + return root.cutStack(index.intValue()); + } else if (name instanceof String) { + if ("top".equals(name)) { + if (root.size() > 0) { + return root.get(0); + } else { + return null; + } + } + + for (Object o : root) { + if (o == null) { + continue; + } + + try { + if ((OgnlRuntime.hasGetProperty(ognlContext, o, name)) || ((o instanceof Map) && ((Map) o).containsKey(name))) { + return OgnlRuntime.getProperty(ognlContext, o, name); + } + } catch (OgnlException e) { + if (e.getReason() != null) { + final String msg = "Caught an Ognl exception while getting property " + name; + throw new XWorkException(msg, e); + } + } catch (IntrospectionException e) { + // this is OK if this happens, we'll just keep trying the next + } + } + + //property was not found + if (context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE)) + throw new NoSuchPropertyException(target, name); + else + return null; + } else { + return null; + } + } + + public Object callMethod(Map context, Object target, String name, Object[] objects) throws MethodFailedException { + CompoundRoot root = (CompoundRoot) target; + + if ("describe".equals(name)) { + Object v; + if (objects != null && objects.length == 1) { + v = objects[0]; + } else { + v = root.get(0); + } + + + if (v instanceof Collection || v instanceof Map || v.getClass().isArray()) { + return v.toString(); + } + + try { + Map descriptors = OgnlRuntime.getPropertyDescriptors(v.getClass()); + + int maxSize = 0; + for (String pdName : descriptors.keySet()) { + if (pdName.length() > maxSize) { + maxSize = pdName.length(); + } + } + + SortedSet set = new TreeSet(); + StringBuffer sb = new StringBuffer(); + for (PropertyDescriptor pd : descriptors.values()) { + + sb.append(pd.getName()).append(": "); + int padding = maxSize - pd.getName().length(); + for (int i = 0; i < padding; i++) { + sb.append(" "); + } + sb.append(pd.getPropertyType().getName()); + set.add(sb.toString()); + + sb = new StringBuffer(); + } + + sb = new StringBuffer(); + for (Object aSet : set) { + String s = (String) aSet; + sb.append(s).append("\n"); + } + + return sb.toString(); + } catch (IntrospectionException e) { + e.printStackTrace(); + } catch (OgnlException e) { + e.printStackTrace(); + } + + return null; + } + + for (Object o : root) { + if (o == null) { + continue; + } + + Class clazz = o.getClass(); + Class[] argTypes = getArgTypes(objects); + + MethodCall mc = null; + + if (argTypes != null) { + mc = new MethodCall(clazz, name, argTypes); + } + + if ((argTypes == null) || !invalidMethods.containsKey(mc)) { + try { + Object value = OgnlRuntime.callMethod((OgnlContext) context, o, name, name, objects); + + if (value != null) { + return value; + } + } catch (OgnlException e) { + // try the next one + Throwable reason = e.getReason(); + + if (!context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE) && (mc != null) && (reason != null) && (reason.getClass() == NoSuchMethodException.class)) { + invalidMethods.put(mc, Boolean.TRUE); + } else if (reason != null) { + throw new MethodFailedException(o, name, e.getReason()); + } + } + } + } + + return null; + } + + public Object callStaticMethod(Map transientVars, Class aClass, String s, Object[] objects) throws MethodFailedException { + return null; + } + + public Class classForName(String className, Map context) throws ClassNotFoundException { + Object root = Ognl.getRoot(context); + + try { + if (root instanceof CompoundRoot) { + if (className.startsWith("vs")) { + CompoundRoot compoundRoot = (CompoundRoot) root; + + if ("vs".equals(className)) { + return compoundRoot.peek().getClass(); + } + + int index = Integer.parseInt(className.substring(2)); + + return compoundRoot.get(index - 1).getClass(); + } + } + } catch (Exception e) { + // just try the old fashioned way + } + + return Thread.currentThread().getContextClassLoader().loadClass(className); + } + + private Class[] getArgTypes(Object[] args) { + if (args == null) { + return EMPTY_CLASS_ARRAY; + } + + Class[] classes = new Class[args.length]; + + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + classes[i] = (arg != null) ? arg.getClass() : Object.class; + } + + return classes; + } + + + static class MethodCall { + Class clazz; + String name; + Class[] args; + int hash; + + public MethodCall(Class clazz, String name, Class[] args) { + this.clazz = clazz; + this.name = name; + this.args = args; + this.hash = clazz.hashCode() + name.hashCode(); + + for (Class arg : args) { + hash += arg.hashCode(); + } + } + + @Override + public boolean equals(Object obj) { + MethodCall mc = (CompoundRootAccessor.MethodCall) obj; + + return (mc.clazz.equals(clazz) && mc.name.equals(name) && Arrays.equals(mc.args, args)); + } + + @Override + public int hashCode() { + return hash; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java new file mode 100644 index 000000000..255a0fce6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java @@ -0,0 +1,29 @@ +/** + * + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + +public class ObjectAccessor extends ObjectPropertyAccessor { + @Override + public Object getProperty(Map map, Object o, Object o1) throws OgnlException { + Object obj = super.getProperty(map, o, o1); + + map.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, o.getClass()); + map.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, o1.toString()); + ReflectionContextState.updateCurrentPropertyPath(map, o1); + return obj; + } + + @Override + public void setProperty(Map map, Object o, Object o1, Object o2) throws OgnlException { + super.setProperty(map, o, o1, o2); + } +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java new file mode 100644 index 000000000..714acf714 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java @@ -0,0 +1,77 @@ +/* + * 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.ognl.accessor; + +import com.opensymphony.xwork2.ognl.ObjectProxy; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.OgnlException; +import ognl.OgnlRuntime; +import ognl.PropertyAccessor; +import ognl.OgnlContext; + +import java.util.Map; + +/** + * Is able to access (set/get) properties on a given object. + *

+ * Uses Ognl internal. + * + * @author Gabe + */ +public class ObjectProxyPropertyAccessor implements PropertyAccessor { + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceAccessor(OgnlContext context, Object target, Object index) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceSetter(OgnlContext context, Object target, Object index) { + return null; + } + + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + ObjectProxy proxy = (ObjectProxy) target; + setupContext(context, proxy); + + return OgnlRuntime.getPropertyAccessor(proxy.getValue().getClass()).getProperty(context, target, name); + + } + + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + ObjectProxy proxy = (ObjectProxy) target; + setupContext(context, proxy); + + OgnlRuntime.getPropertyAccessor(proxy.getValue().getClass()).setProperty(context, target, name, value); + } + + /** + * Sets up the context with the last property and last class + * accessed. + * + * @param context + * @param proxy + */ + private void setupContext(Map context, ObjectProxy proxy) { + ReflectionContextState.setLastBeanClassAccessed(context, proxy.getLastClassAccessed()); + ReflectionContextState.setLastBeanPropertyAccessed(context, proxy.getLastPropertyAccessed()); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java new file mode 100644 index 000000000..32cf2edca --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java @@ -0,0 +1,277 @@ +/* + * 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.ognl.accessor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; +import ognl.OgnlRuntime; +import ognl.SetPropertyAccessor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Gabe + */ +public class XWorkCollectionPropertyAccessor extends SetPropertyAccessor { + + private static final Logger LOG = LoggerFactory.getLogger(XWorkCollectionPropertyAccessor.class); + private static final String CONTEXT_COLLECTION_MAP = "xworkCollectionPropertyAccessorContextSetMap"; + + public static final String KEY_PROPERTY_FOR_CREATION = "makeNew"; + + //use a basic object Ognl property accessor here + //to access properties of the objects in the Set + //so that nothing is put in the context to screw things up + private ObjectPropertyAccessor _accessor = new ObjectPropertyAccessor(); + + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { + this.objectTypeDeterminer = ot; + } + + /** + * Gets the property of a Collection by indexing the collection + * based on a key property. For example, if the key property were + * 'id', this method would convert the key Object to whatever + * type the id property was, and then access the Set like it was + * a Map returning a JavaBean with the value of id property matching + * the input. + * + * @see ognl.PropertyAccessor#getProperty(java.util.Map, Object, Object) + */ + @Override + public Object getProperty(Map context, Object target, Object key) + throws OgnlException { + + LOG.debug("Entering getProperty()"); + + //check if it is a generic type property. + //if so, return the value from the + //superclass which will determine this. + if (!ReflectionContextState.isGettingByKeyProperty(context) + && !key.equals(KEY_PROPERTY_FOR_CREATION)) { + return super.getProperty(context, target, key); + } else { + //reset context property + ReflectionContextState.setGettingByKeyProperty(context,false); + } + Collection c = (Collection) target; + + //get the bean that this collection is a property of + Class lastBeanClass = ReflectionContextState.getLastBeanClassAccessed(context); + + //get the property name that this collection uses + String lastPropertyClass = ReflectionContextState.getLastBeanPropertyAccessed(context); + + //if one or the other is null, assume that it isn't + //set up correctly so just return whatever the + //superclass would + if (lastBeanClass == null || lastPropertyClass == null) { + ReflectionContextState.updateCurrentPropertyPath(context, key); + return super.getProperty(context, target, key); + } + + + //get the key property to index the + //collection with from the ObjectTypeDeterminer + String keyProperty = objectTypeDeterminer + .getKeyProperty(lastBeanClass, lastPropertyClass); + + //get the collection class of the + Class collClass = objectTypeDeterminer.getElementClass(lastBeanClass, lastPropertyClass, key); + + Class keyType = null; + Class toGetTypeFrom = (collClass != null) ? collClass : c.iterator().next().getClass(); + try { + keyType = OgnlRuntime.getPropertyDescriptor(toGetTypeFrom, keyProperty).getPropertyType(); + } catch (Exception exc) { + throw new OgnlException("Error getting property descriptor: " + exc.getMessage()); + } + + + if (ReflectionContextState.isCreatingNullObjects(context)) { + Map collMap = getSetMap(context, c, keyProperty, collClass); + if (key.toString().equals(KEY_PROPERTY_FOR_CREATION)) { + //this should return the XWorkList + //for this set that contains new entries + //then the ListPropertyAccessor will be called + //to access it in the next sequence + return collMap.get(null); + } + Object realKey = xworkConverter.convertValue(context, key, keyType); + Object value = collMap.get(realKey); + if (value == null + && ReflectionContextState.isCreatingNullObjects(context) + && objectTypeDeterminer + .shouldCreateIfNew(lastBeanClass,lastPropertyClass,c,keyProperty,false)) { + //create a new element and + //set the value of keyProperty + //to be the given value + try { + value=objectFactory.buildBean(collClass, context); + + //set the value of the keyProperty + _accessor.setProperty(context,value,keyProperty,realKey); + + //add the new object to the collection + c.add(value); + + //add to the Map if accessed later + collMap.put(realKey, value); + + + } catch (Exception exc) { + throw new OgnlException("Error adding new element to collection", exc); + + } + + } + return value; + } else { + if (key.toString().equals(KEY_PROPERTY_FOR_CREATION)) { + return null; + } + //with getting do iteration + //don't assume for now it is + //optimized to create the Map + //and unlike setting, there is + //no easy key for the Set + Object realKey = xworkConverter.convertValue(context, key, keyType); + return getPropertyThroughIteration(context, c, keyProperty, realKey); + } + } + + /* + * Gets an indexed Map by a given key property with the key being + * the value of the property and the value being the + */ + private Map getSetMap(Map context, Collection collection, String property, Class valueClass) + throws OgnlException { + LOG.debug("getting set Map"); + String path = ReflectionContextState.getCurrentPropertyPath(context); + Map map = ReflectionContextState.getSetMap(context, + path); + + if (map == null) { + LOG.debug("creating set Map"); + map = new HashMap(); + map.put(null, new SurrugateList(collection)); + for (Object currTest : collection) { + Object currKey = _accessor.getProperty(context, currTest, property); + if (currKey != null) { + map.put(currKey, currTest); + } + } + ReflectionContextState.setSetMap(context, map, path); + } + return map; + } + + /* + * gets a bean with the given + */ + public Object getPropertyThroughIteration(Map context, Collection collection, String property, Object key) + throws OgnlException { + //TODO + for (Object currTest : collection) { + if (_accessor.getProperty(context, currTest, property).equals(key)) { + return currTest; + } + } + //none found + return null; + } + + @Override + public void setProperty(Map arg0, Object arg1, Object arg2, Object arg3) + throws OgnlException { + + super.setProperty(arg0, arg1, arg2, arg3); + } +} + +/** + * @author Gabe + */ +class SurrugateList extends ArrayList { + + private Collection surrugate; + + public SurrugateList(Collection surrugate) { + this.surrugate = surrugate; + } + + @Override + public void add(int arg0, Object arg1) { + if (arg1 != null) { + surrugate.add(arg1); + } + super.add(arg0, arg1); + } + + @Override + public boolean add(Object arg0) { + if (arg0 != null) { + surrugate.add(arg0); + } + return super.add(arg0); + } + + @Override + public boolean addAll(Collection arg0) { + surrugate.addAll(arg0); + return super.addAll(arg0); + } + + @Override + public boolean addAll(int arg0, Collection arg1) { + surrugate.addAll(arg1); + return super.addAll(arg0, arg1); + } + + @Override + public Object set(int arg0, Object arg1) { + if (arg1 != null) { + surrugate.add(arg1); + } + return super.set(arg0, arg1); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkEnumerationAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkEnumerationAccessor.java new file mode 100644 index 000000000..88e6408b3 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkEnumerationAccessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import ognl.EnumerationPropertyAccessor; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + + +/** + * @author plightbo + */ +public class XWorkEnumerationAccessor extends EnumerationPropertyAccessor { + + ObjectPropertyAccessor opa = new ObjectPropertyAccessor(); + + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + opa.setProperty(context, target, name, value); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.java new file mode 100644 index 000000000..7afe3f56f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import ognl.IteratorPropertyAccessor; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + + +/** + * @author plightbo + */ +public class XWorkIteratorPropertyAccessor extends IteratorPropertyAccessor { + + ObjectPropertyAccessor opa = new ObjectPropertyAccessor(); + + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + opa.setProperty(context, target, name, value); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java new file mode 100644 index 000000000..6dab13bf3 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java @@ -0,0 +1,182 @@ +/* + * 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.ognl.accessor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ListPropertyAccessor; +import ognl.OgnlException; +import ognl.PropertyAccessor; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Overrides the list property accessor so in the case of trying + * to add properties of a given bean and the JavaBean is not present, + * this class will create the necessary blank JavaBeans. + * + * @author Gabriel Zimmerman + */ +public class XWorkListPropertyAccessor extends ListPropertyAccessor { + + private XWorkCollectionPropertyAccessor _sAcc = new XWorkCollectionPropertyAccessor(); + + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + private OgnlUtil ognlUtil; + + @Inject("java.util.Collection") + public void setXWorkCollectionPropertyAccessor(PropertyAccessor acc) { + this._sAcc = (XWorkCollectionPropertyAccessor) acc; + } + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { + this.objectTypeDeterminer = ot; + } + + @Inject + public void setOgnlUtil(OgnlUtil util) { + this.ognlUtil = util; + } + + @Override + public Object getProperty(Map context, Object target, Object name) + throws OgnlException { + + if (ReflectionContextState.isGettingByKeyProperty(context) + || name.equals(XWorkCollectionPropertyAccessor.KEY_PROPERTY_FOR_CREATION)) { + return _sAcc.getProperty(context, target, name); + } else if (name instanceof String) { + return super.getProperty(context, target, name); + } + ReflectionContextState.updateCurrentPropertyPath(context, name); + //System.out.println("Entering XWorkListPropertyAccessor. Name: " + name); + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + + if (name instanceof Number + && ReflectionContextState.isCreatingNullObjects(context) + && objectTypeDeterminer.shouldCreateIfNew(lastClass,lastProperty,target,null,true)) { + + //System.out.println("Getting index from List"); + List list = (List) target; + int index = ((Number) name).intValue(); + int listSize = list.size(); + + if (lastClass == null || lastProperty == null) { + return super.getProperty(context, target, name); + } + Class beanClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, name); + if (listSize <= index) { + Object result = null; + + for (int i = listSize; i < index; i++) { + + list.add(null); + + } + try { + list.add(index, result = objectFactory.buildBean(beanClass, context)); + } catch (Exception exc) { + throw new XWorkException(exc); + } + return result; + } else if (list.get(index) == null) { + Object result = null; + try { + list.set(index, result = objectFactory.buildBean(beanClass, context)); + } catch (Exception exc) { + throw new XWorkException(exc); + } + return result; + } + } + return super.getProperty(context, target, name); + } + + @Override + public void setProperty(Map context, Object target, Object name, Object value) + throws OgnlException { + + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + Class convertToClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, name); + + if (name instanceof String && value.getClass().isArray()) { + // looks like the input game in the form of "someList.foo" and + // we are expected to define the index values ourselves. + // So let's do it: + + Collection c = (Collection) target; + Object[] values = (Object[]) value; + for (Object v : values) { + try { + Object o = objectFactory.buildBean(convertToClass, context); + ognlUtil.setValue((String) name, context, o, v); + c.add(o); + } catch (Exception e) { + throw new OgnlException("Error converting given String values for Collection.", e); + } + } + + // we don't want to do the normal list property setting now, since we've already done the work + // just return instead + return; + } + + Object realValue = getRealValue(context, value, convertToClass); + + if (target instanceof List && name instanceof Number) { + //make sure there are enough spaces in the List to set + List list = (List) target; + int listSize = list.size(); + int count = ((Number) name).intValue(); + if (count >= listSize) { + for (int i = listSize; i <= count; i++) { + list.add(null); + } + } + } + + super.setProperty(context, target, name, realValue); + } + + private Object getRealValue(Map context, Object value, Class convertToClass) { + if (value == null || convertToClass == null) { + return value; + } + return xworkConverter.convertValue(context, value, convertToClass); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java new file mode 100644 index 000000000..b7d3c10b7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java @@ -0,0 +1,176 @@ +/* + * 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.ognl.accessor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.MapPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + +/** + * Implementation of PropertyAccessor that sets and gets properties by storing and looking + * up values in Maps. + * + * @author Gabriel Zimmerman + */ +public class XWorkMapPropertyAccessor extends MapPropertyAccessor { + + private static final Logger LOG = LoggerFactory.getLogger(XWorkMapPropertyAccessor.class); + + private static final String[] INDEX_ACCESS_PROPS = new String[] + {"size", "isEmpty", "keys", "values"}; + + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { + this.objectTypeDeterminer = ot; + } + + @Override + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + + if (LOG.isDebugEnabled()) { + LOG.debug("Entering getProperty ("+context+","+target+","+name+")"); + } + + ReflectionContextState.updateCurrentPropertyPath(context, name); + // if this is one of the regular index access + // properties then just let the superclass deal with the + // get. + if (name instanceof String && contains(INDEX_ACCESS_PROPS, (String) name)) { + return super.getProperty(context, target, name); + } + + Object result = null; + + try{ + result = super.getProperty(context, target, name); + } catch(ClassCastException ex){ + } + + if (result == null) { + //find the key class and convert the name to that class + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + if (lastClass == null || lastProperty == null) { + return super.getProperty(context, target, name); + } + Class keyClass = objectTypeDeterminer + .getKeyClass(lastClass, lastProperty); + + if (keyClass == null) { + + keyClass = java.lang.String.class; + } + Object key = getKey(context, name); + Map map = (Map) target; + result = map.get(key); + + if (result == null && + context.get(ReflectionContextState.CREATE_NULL_OBJECTS) != null + && objectTypeDeterminer.shouldCreateIfNew(lastClass,lastProperty,target,null,false)) { + Class valueClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, key); + + try { + result = objectFactory.buildBean(valueClass, context); + map.put(key, result); + } catch (Exception exc) { + + } + + } + } + return result; + } + + /** + * @param array + * @param name + */ + private boolean contains(String[] array, String name) { + for (String anArray : array) { + if (anArray.equals(name)) { + return true; + } + } + + return false; + } + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + if (LOG.isDebugEnabled()) { + LOG.debug("Entering setProperty("+context+","+target+","+name+","+value+")"); + } + + Object key = getKey(context, name); + Map map = (Map) target; + map.put(key, getValue(context, value)); + } + + private Object getValue(Map context, Object value) { + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + if (lastClass == null || lastProperty == null) { + return value; + } + Class elementClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, null); + if (elementClass == null) { + return value; // nothing is specified, we assume it will be the value passed in. + } + return xworkConverter.convertValue(context, value, elementClass); +} + + private Object getKey(Map context, Object name) { + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + if (lastClass == null || lastProperty == null) { + // return java.lang.String.class; + // commented out the above -- it makes absolutely no sense for when setting basic maps! + return name; + } + Class keyClass = objectTypeDeterminer.getKeyClass(lastClass, lastProperty); + if (keyClass == null) { + keyClass = java.lang.String.class; + } + + return xworkConverter.convertValue(context, name, keyClass); + + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java new file mode 100644 index 000000000..11d3f315f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java @@ -0,0 +1,159 @@ +/* + * 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.ognl.accessor; + +import java.beans.PropertyDescriptor; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import ognl.MethodFailedException; +import ognl.ObjectMethodAccessor; +import ognl.OgnlContext; +import ognl.OgnlRuntime; +import ognl.PropertyAccessor; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; + + +/** + * Allows methods to be executed under normal cirumstances, except when {@link ReflectionContextState#DENY_METHOD_EXECUTION} + * is in the action context with a value of true. + * + * @author Patrick Lightbody + * @author tmjee + */ +public class XWorkMethodAccessor extends ObjectMethodAccessor { + + private static final Logger LOG = LoggerFactory.getLogger(XWorkMethodAccessor.class); + + /** + * @deprecated Use {@link ReflectionContextState#DENY_METHOD_EXECUTION} instead + */ + @Deprecated public static final String DENY_METHOD_EXECUTION = ReflectionContextState.DENY_METHOD_EXECUTION; + /** + * @deprecated Use {@link ReflectionContextState#DENY_INDEXED_ACCESS_EXECUTION} instead + */ + @Deprecated public static final String DENY_INDEXED_ACCESS_EXECUTION = ReflectionContextState.DENY_INDEXED_ACCESS_EXECUTION; + + + @Override + public Object callMethod(Map context, Object object, String string, Object[] objects) throws MethodFailedException { + + //Collection property accessing + //this if statement ensures that ognl + //statements of the form someBean.mySet('keyPropVal') + //return the set element with value of the keyProp given + + if (objects.length==1 + && context instanceof OgnlContext) { + try { + OgnlContext ogContext=(OgnlContext)context; + if (OgnlRuntime.hasSetProperty(ogContext, object, string)) { + PropertyDescriptor descriptor=OgnlRuntime.getPropertyDescriptor(object.getClass(), string); + Class propertyType=descriptor.getPropertyType(); + if ((Collection.class).isAssignableFrom(propertyType)) { + //go directly through OgnlRuntime here + //so that property strings are not cleared + //i.e. OgnlUtil should be used initially, OgnlRuntime + //thereafter + + Object propVal=OgnlRuntime.getProperty(ogContext, object, string); + //use the Collection property accessor instead of the individual property accessor, because + //in the case of Lists otherwise the index property could be used + PropertyAccessor accessor=OgnlRuntime.getPropertyAccessor(Collection.class); + ReflectionContextState.setGettingByKeyProperty(ogContext,true); + return accessor.getProperty(ogContext,propVal,objects[0]); + } + } + } catch (Exception oe) { + //this exception should theoretically never happen + //log it + LOG.error("An unexpected exception occurred", oe); + } + + } + + //HACK - we pass indexed method access i.e. setXXX(A,B) pattern + if ( + (objects.length == 2 && string.startsWith("set")) + || + (objects.length == 1 && string.startsWith("get")) + ) { + Boolean exec = (Boolean) context.get(ReflectionContextState.DENY_INDEXED_ACCESS_EXECUTION); + boolean e = ((exec == null) ? false : exec.booleanValue()); + if (!e) { + return callMethodWithDebugInfo(context, object, string, objects); + } + } + Boolean exec = (Boolean) context.get(ReflectionContextState.DENY_METHOD_EXECUTION); + boolean e = ((exec == null) ? false : exec.booleanValue()); + + if (!e) { + return callMethodWithDebugInfo(context, object, string, objects); + } else { + return null; + } + } + + private Object callMethodWithDebugInfo(Map context, Object object, String methodName, + Object[] objects) throws MethodFailedException { + try { + return super.callMethod(context, object, methodName, objects); + } + catch(MethodFailedException e) { + if (LOG.isDebugEnabled()) { + if (!(e.getReason() instanceof NoSuchMethodException)) { + // the method exists on the target object, but something went wrong + String s = "Error calling method through OGNL: object: [#0] method: [#1] args: [#2]"; + LOG.debug(s, e.getReason(), object.toString(), methodName, Arrays.toString(objects)); + } + } + throw e; + } + } + + @Override + public Object callStaticMethod(Map context, Class aClass, String string, Object[] objects) throws MethodFailedException { + Boolean exec = (Boolean) context.get(ReflectionContextState.DENY_METHOD_EXECUTION); + boolean e = ((exec == null) ? false : exec.booleanValue()); + + if (!e) { + return callStaticMethodWithDebugInfo(context, aClass, string, objects); + } else { + return null; + } + } + + private Object callStaticMethodWithDebugInfo(Map context, Class aClass, String methodName, + Object[] objects) throws MethodFailedException { + try { + return super.callStaticMethod(context, aClass, methodName, objects); + } + catch(MethodFailedException e) { + if (LOG.isDebugEnabled()) { + if (!(e.getReason() instanceof NoSuchMethodException)) { + // the method exists on the target class, but something went wrong + String s = "Error calling method through OGNL, class: [#0] method: [#1] args: [#2]"; + LOG.debug(s, e.getReason(), aClass.getName(), methodName, Arrays.toString(objects)); + } + } + throw e; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.java new file mode 100644 index 000000000..5351401bf --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.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.ognl.accessor; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + +/** + * @author Gabe + */ +public class XWorkObjectPropertyAccessor extends ObjectPropertyAccessor { + @Override + public Object getProperty(Map context, Object target, Object oname) + throws OgnlException { + //set the last set objects in the context + //so if the next objects accessed are + //Maps or Collections they can use the information + //to determine conversion types + context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, target.getClass()); + context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, oname.toString()); + ReflectionContextState.updateCurrentPropertyPath(context, oname); + return super.getProperty(context, target, oname); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/package.html new file mode 100644 index 000000000..379415476 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/package.html @@ -0,0 +1 @@ +Main XWork interfaces and classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java new file mode 100644 index 000000000..e7e174a4b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java @@ -0,0 +1,274 @@ +/* + * 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.spring; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * Simple implementation of the ObjectFactory that makes use of Spring's application context if one has been configured, + * before falling back on the default mechanism of instantiating a new class using the class name.

In order to use + * this class in your application, you will need to instantiate a copy of this class and set it as XWork's ObjectFactory + * before the xwork.xml file is parsed. In a servlet environment, this could be done using a ServletContextListener. + * + * @author Simon Stewart (sms@lateral.net) + */ +public class SpringObjectFactory extends ObjectFactory implements ApplicationContextAware { + private static final Logger LOG = LoggerFactory.getLogger(SpringObjectFactory.class); + + protected ApplicationContext appContext; + protected AutowireCapableBeanFactory autoWiringFactory; + protected int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; + private final Map classes = new HashMap(); + private boolean useClassCache = true; + private boolean alwaysRespectAutowireStrategy = false; + + @Inject(value="applicationContextPath",required=false) + public void setApplicationContextPath(String ctx) { + if (ctx != null) { + setApplicationContext(new ClassPathXmlApplicationContext(ctx)); + } + } + + /** + * Set the Spring ApplicationContext that should be used to look beans up with. + * + * @param appContext The Spring ApplicationContext that should be used to look beans up with. + */ + public void setApplicationContext(ApplicationContext appContext) + throws BeansException { + this.appContext = appContext; + autoWiringFactory = findAutoWiringBeanFactory(this.appContext); + } + + /** + * Sets the autowiring strategy + * + * @param autowireStrategy + */ + public void setAutowireStrategy(int autowireStrategy) { + switch (autowireStrategy) { + case AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT: + LOG.info("Setting autowire strategy to autodetect"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_BY_NAME: + LOG.info("Setting autowire strategy to name"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE: + LOG.info("Setting autowire strategy to type"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR: + LOG.info("Setting autowire strategy to constructor"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_NO: + LOG.info("Setting autowire strategy to none"); + this.autowireStrategy = autowireStrategy; + break; + default: + throw new IllegalStateException("Invalid autowire type set"); + } + } + + public int getAutowireStrategy() { + return autowireStrategy; + } + + + /** + * If the given context is assignable to AutowireCapbleBeanFactory or contains a parent or a factory that is, then + * set the autoWiringFactory appropriately. + * + * @param context + */ + protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) { + if (context instanceof AutowireCapableBeanFactory) { + // Check the context + return (AutowireCapableBeanFactory) context; + } else if (context instanceof ConfigurableApplicationContext) { + // Try and grab the beanFactory + return ((ConfigurableApplicationContext) context).getBeanFactory(); + } else if (context.getParent() != null) { + // And if all else fails, try again with the parent context + return findAutoWiringBeanFactory(context.getParent()); + } + return null; + } + + /** + * Looks up beans using Spring's application context before falling back to the method defined in the {@link + * ObjectFactory}. + * + * @param beanName The name of the bean to look up in the application context + * @param extraContext + * @return A bean from Spring or the result of calling the overridden + * method. + * @throws Exception + */ + @Override + public Object buildBean(String beanName, Map extraContext, boolean injectInternal) throws Exception { + Object o = null; + try { + o = appContext.getBean(beanName); + } catch (NoSuchBeanDefinitionException e) { + Class beanClazz = getClassInstance(beanName); + o = buildBean(beanClazz, extraContext); + } + if (injectInternal) { + injectInternalBeans(o); + } + return o; + } + + /** + * @param clazz + * @param extraContext + * @throws Exception + */ + @Override + public Object buildBean(Class clazz, Map extraContext) throws Exception { + Object bean; + + try { + // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies + if (alwaysRespectAutowireStrategy) { + // Leave the creation up to Spring + bean = autoWiringFactory.createBean(clazz, autowireStrategy, false); + injectApplicationContext(bean); + return injectInternalBeans(bean); + } else { + bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); + bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName()); + // We don't need to call the init-method since one won't be registered. + bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName()); + return autoWireBean(bean, autoWiringFactory); + } + } catch (UnsatisfiedDependencyException e) { + if (LOG.isErrorEnabled()) + LOG.error("Error building bean", e); + // Fall back + return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory); + } + } + + public Object autoWireBean(Object bean) { + return autoWireBean(bean, autoWiringFactory); + } + + /** + * @param bean + * @param autoWiringFactory + */ + public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) { + if (autoWiringFactory != null) { + autoWiringFactory.autowireBeanProperties(bean, + autowireStrategy, false); + } + injectApplicationContext(bean); + + injectInternalBeans(bean); + + return bean; + } + + private void injectApplicationContext(Object bean) { + if (bean instanceof ApplicationContextAware) { + ((ApplicationContextAware) bean).setApplicationContext(appContext); + } + } + + public Class getClassInstance(String className) throws ClassNotFoundException { + Class clazz = null; + if (useClassCache) { + synchronized(classes) { + // this cache of classes is needed because Spring sucks at dealing with situations where the + // class instance changes + clazz = (Class) classes.get(className); + } + } + + if (clazz == null) { + if (appContext.containsBean(className)) { + clazz = appContext.getBean(className).getClass(); + } else { + clazz = super.getClassInstance(className); + } + + if (useClassCache) { + synchronized(classes) { + classes.put(className, clazz); + } + } + } + + return clazz; + } + + /** + * This method sets the ObjectFactory used by XWork to this object. It's best used as the "init-method" of a Spring + * bean definition in order to hook Spring and XWork together properly (as an alternative to the + * org.apache.struts2.spring.lifecycle.SpringObjectFactoryListener) + * @deprecated Since 2.1 as it isn't necessary + */ + @Deprecated public void initObjectFactory() { + // not necessary anymore + } + + /** + * Allows for ObjectFactory implementations that support + * Actions without no-arg constructors. + * + * @return false + */ + @Override + public boolean isNoArgConstructorRequired() { + return false; + } + + /** + * Enable / disable caching of classes loaded by Spring. + * + * @param useClassCache + */ + public void setUseClassCache(boolean useClassCache) { + this.useClassCache = useClassCache; + } + + /** + * Determines if the autowire strategy is always followed when creating beans + * + * @param alwaysRespectAutowireStrategy True if the strategy is always used + */ + public void setAlwaysRespectAutowireStrategy(boolean alwaysRespectAutowireStrategy) { + this.alwaysRespectAutowireStrategy = alwaysRespectAutowireStrategy; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java new file mode 100644 index 000000000..db3d89d35 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java @@ -0,0 +1,96 @@ +/* + * 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.spring; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * SpringProxyableObjectFactory. + * + * @author Jason Carreira + */ +public class SpringProxyableObjectFactory extends SpringObjectFactory { + + private static final Logger LOG = LoggerFactory.getLogger(SpringProxyableObjectFactory.class); + + private List skipBeanNames = new ArrayList(); + + @Override + public Object buildBean(String beanName, Map extraContext) throws Exception { + if (LOG.isDebugEnabled()) { + LOG.debug("Building bean for name " + beanName); + } + if (!skipBeanNames.contains(beanName)) { + ApplicationContext anAppContext = getApplicationContext(extraContext); + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Trying the application context... appContext = " + anAppContext + ",\n bean name = " + beanName); + } + return anAppContext.getBean(beanName); + } catch (NoSuchBeanDefinitionException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Did not find bean definition for bean named " + beanName + ", creating a new one..."); + } + if (autoWiringFactory instanceof BeanDefinitionRegistry) { + try { + Class clazz = Class.forName(beanName); + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) autoWiringFactory; + RootBeanDefinition def = new RootBeanDefinition(clazz, autowireStrategy); + def.setSingleton(false); + if (LOG.isDebugEnabled()) { + LOG.debug("Registering a new bean definition for class " + beanName); + } + registry.registerBeanDefinition(beanName,def); + try { + return anAppContext.getBean(beanName); + } catch (NoSuchBeanDefinitionException e2) { + LOG.warn("Could not register new bean definition for bean " + beanName); + skipBeanNames.add(beanName); + } + } catch (ClassNotFoundException e1) { + skipBeanNames.add(beanName); + } + } + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Returning autowired instance created by default ObjectFactory"); + } + return autoWireBean(super.buildBean(beanName, extraContext), autoWiringFactory); + } + + /** + * Subclasses may override this to return a different application context. + * Note that this application context should see any changes made to the + * autoWiringFactory, so the application context should be either + * the original or a child context of the original. + * + * @param context provided context. + */ + protected ApplicationContext getApplicationContext(Map context) { + return appContext; + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java new file mode 100644 index 000000000..c76ccc254 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java @@ -0,0 +1,136 @@ +/* + * 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.spring.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.spring.SpringObjectFactory; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.web.context.WebApplicationContext; + +/** + * + * TODO: Give a description of the Interceptor. + * + * + * + * TODO: Describe the paramters for this Interceptor. + * + * + * + * TODO: Discuss some possible extension of the Interceptor. + * + * + *

+ * 
+ * <!-- TODO: Describe how the Interceptor reference will effect execution -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *      TODO: fill in the interceptor reference.
+ *     <interceptor-ref name=""/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * Autowires action classes to Spring beans. The strategy for autowiring the beans can be configured + * by setting the parameter on the interceptor. Actions that need access to the ActionContext + * can implements the ApplicationContextAware interface. The context will also be placed on + * the action context under the APPLICATION_CONTEXT attribute. + * + * @author Simon Stewart + * @author Eric Hauser + */ +public class ActionAutowiringInterceptor extends AbstractInterceptor implements ApplicationContextAware { + private static final Logger LOG = LoggerFactory.getLogger(ActionAutowiringInterceptor.class); + + public static final String APPLICATION_CONTEXT = "com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor.applicationContext"; + + private boolean initialized = false; + private ApplicationContext context; + private SpringObjectFactory factory; + private Integer autowireStrategy; + + /** + * @param autowireStrategy + */ + public void setAutowireStrategy(Integer autowireStrategy) { + this.autowireStrategy = autowireStrategy; + } + + /** + * Looks for the ApplicationContext under the attribute that the Spring listener sets in + * the servlet context. The configuration is done the first time here instead of in init() since the + * ActionContext is not available during Interceptor initialization. + *

+ * Autowires the action to Spring beans and places the ApplicationContext + * on the ActionContext + *

+ * TODO Should this check to see if the SpringObjectFactory has already been configured + * instead of instantiating a new one? Or is there a good reason for the interceptor to have it's own + * factory? + * + * @param invocation + * @throws Exception + */ + @Override public String intercept(ActionInvocation invocation) throws Exception { + if (!initialized) { + ApplicationContext applicationContext = (ApplicationContext) ActionContext.getContext().getApplication().get( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + + if (applicationContext == null) { + LOG.warn("ApplicationContext could not be found. Action classes will not be autowired."); + } else { + setApplicationContext(applicationContext); + factory = new SpringObjectFactory(); + factory.setApplicationContext(getApplicationContext()); + if (autowireStrategy != null) { + factory.setAutowireStrategy(autowireStrategy.intValue()); + } + } + initialized = true; + } + + if (factory != null) { + Object bean = invocation.getAction(); + factory.autoWireBean(bean); + + ActionContext.getContext().put(APPLICATION_CONTEXT, context); + } + return invocation.invoke(); + } + + /** + * @param applicationContext + * @throws BeansException + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } + + /** + * @return context + */ + protected ApplicationContext getApplicationContext() { + return context; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html new file mode 100644 index 000000000..6579e80e6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html @@ -0,0 +1 @@ +Spring specific interceptor classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/spring/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/package.html new file mode 100644 index 000000000..91d2d95a5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/spring/package.html @@ -0,0 +1 @@ +Spring ObjectFactory classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java new file mode 100644 index 000000000..309db0623 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java @@ -0,0 +1,36 @@ +package com.opensymphony.xwork2.test; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +public class StubConfigurationProvider implements ConfigurationProvider { + + public void destroy() { + // TODO Auto-generated method stub + + } + + public void init(Configuration configuration) throws ConfigurationException { + // TODO Auto-generated method stub + } + + public void loadPackages() throws ConfigurationException { + // TODO Auto-generated method stub + + } + + public boolean needsReload() { + // TODO Auto-generated method stub + return false; + } + + public void register(ContainerBuilder builder, LocatableProperties props) + throws ConfigurationException { + // TODO Auto-generated method stub + + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java new file mode 100644 index 000000000..a9462a239 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java @@ -0,0 +1,227 @@ +/* + * 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.util; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * AnnotationUtils + * + * Various utility methods dealing with annotations + * + * @author Rainer Hermanns + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Dan Oxlade, dan d0t oxlade at gmail d0t c0m + * @version $Id$ + */ +public class AnnotationUtils { + + private static final Pattern SETTER_PATTERN = Pattern.compile("set([A-Z][A-Za-z0-9]*)$"); + private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is|has)([A-Z][A-Za-z0-9]*)$"); + + + + /** + * Adds all fields with the specified Annotation of class clazz and its superclasses to allFields + * + * @param annotationClass + * @param clazz + * @param allFields + */ + public static void addAllFields(Class annotationClass, Class clazz, List allFields) { + + if (clazz == null) { + return; + } + + Field[] fields = clazz.getDeclaredFields(); + + for (Field field : fields) { + Annotation ann = field.getAnnotation(annotationClass); + if (ann!=null) { + allFields.add(field); + } + } + addAllFields(annotationClass, clazz.getSuperclass(), allFields); + } + + /** + * Adds all methods with the specified Annotation of class clazz and its superclasses to allFields + * + * @param annotationClass + * @param clazz + * @param allMethods + */ + public static void addAllMethods(Class annotationClass, Class clazz, List allMethods) { + + if (clazz == null) { + return; + } + + Method[] methods = clazz.getDeclaredMethods(); + + for (Method method : methods) { + Annotation ann = method.getAnnotation(annotationClass); + if (ann!=null) { + allMethods.add(method); + } + } + addAllMethods(annotationClass, clazz.getSuperclass(), allMethods); + } + + /** + * + * @param clazz + * @param allInterfaces + */ + public static void addAllInterfaces(Class clazz, List allInterfaces) { + if (clazz == null) { + return; + } + + Class[] interfaces = clazz.getInterfaces(); + allInterfaces.addAll(Arrays.asList(interfaces)); + addAllInterfaces(clazz.getSuperclass(), allInterfaces); + } + + /** + * For the given Class get a collection of the the {@link AnnotatedElement}s + * that match the given annotations or if no annotations are + * specified then return all of the annotated elements of the given Class. + * Includes only the method level annotations. + * + * @param clazz The {@link Class} to inspect + * @param annotation the {@link Annotation}s to find + * @return A {@link Collection}<{@link AnnotatedElement}> containing all of the + * method {@link AnnotatedElement}s matching the specified {@link Annotation}s + */ + public static final Collection getAnnotatedMethods(Class clazz, Class... annotation){ + Collection toReturn = new HashSet(); + + for(Method m : clazz.getMethods()){ + if( ArrayUtils.isNotEmpty(annotation) && isAnnotatedBy(m,annotation) ){ + toReturn.add(m); + }else if( ArrayUtils.isEmpty(annotation) && ArrayUtils.isNotEmpty(m.getAnnotations())){ + toReturn.add(m); + } + } + + return toReturn; + } + + /** + * Varargs version of AnnotatedElement.isAnnotationPresent() + * @see AnnotatedElement + */ + public static final boolean isAnnotatedBy(AnnotatedElement annotatedElement, Class... annotation) { + if(ArrayUtils.isEmpty(annotation)) return false; + + for( Class c : annotation ){ + if( annotatedElement.isAnnotationPresent(c) ) return true; + } + + return false; + } + + /** + * + * @deprecated since 2.0.4 use getAnnotatedMethods + */ + @Deprecated + public static List findAnnotatedMethods(Class clazz, Class annotationClass) { + List methods = new ArrayList(); + findRecursively(clazz, annotationClass, methods); + return methods; + } + + /** + * + * @deprecated since 2.0.4 use getAnnotatedMethods + */ + @Deprecated + public static void findRecursively(Class clazz, Class annotationClass, List methods) { + for (Method m : clazz.getDeclaredMethods()) { + if (m.getAnnotation(annotationClass) != null) { methods.add(0, m); } + } + if (clazz.getSuperclass() != Object.class) { + findRecursively(clazz.getSuperclass(), annotationClass, methods); + } + } + + /** + * Returns the property name for a method. + * This method is independant from property fields. + * + * @param method The method to get the property name for. + * @return the property name for given method; null if non could be resolved. + */ + public static String resolvePropertyName(Method method) { + + Matcher matcher = SETTER_PATTERN.matcher(method.getName()); + if (matcher.matches() && method.getParameterTypes().length == 1) { + String raw = matcher.group(1); + return raw.substring(0, 1).toLowerCase() + raw.substring(1); + } + + matcher = GETTER_PATTERN.matcher(method.getName()); + if (matcher.matches() && method.getParameterTypes().length == 0) { + String raw = matcher.group(2); + return raw.substring(0, 1).toLowerCase() + raw.substring(1); + } + + return null; + } + + + /** + * Retrieves all classes within a packages. + * TODO: this currently does not work with jars. + * + * @param pckgname + * @return Array of full qualified class names from this package. + */ + public static String[] find(Class clazz, final String pckgname) { + + List classes = new ArrayList(); + String name = new String(pckgname); + if (!name.startsWith("/")) { + name = "/" + name; + } + + name = name.replace('.', File.separatorChar); + + final URL url = clazz.getResource(name); + final File directory = new File(url.getFile()); + + if (directory.exists()) { + final String[] files = directory.list(); + for (String file : files) { + if (file.endsWith(".class")) { + classes.add(pckgname + "." + file.substring(0, file.length() - 6)); + } + } + } + return classes.toArray(new String[classes.size()]); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.java new file mode 100644 index 000000000..01ff02364 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.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.util; + +/** + * @author Dan Oxlade, dan d0t oxlade at gmail d0t c0m + */ +public class ArrayUtils { + + public static boolean isEmpty(Object[] array) { + return null == array || array.length == 0; + } + + public static boolean isNotEmpty(Object[] array) { + return !isEmpty(array); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java new file mode 100644 index 000000000..54f113e83 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java @@ -0,0 +1,235 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; + + +/** + * This class is extremely useful for loading resources and classes in a fault tolerant manner + * that works across different applications servers. + * + * It has come out of many months of frustrating use of multiple application servers at Atlassian, + * please don't change things unless you're sure they're not going to break in one server or another! + * + * It was brought in from oscore trunk revision 147. + * + * @author $Author$ + * @version $Revision$ + */ +public class ClassLoaderUtil { + //~ Methods //////////////////////////////////////////////////////////////// + + /** + * Load all resources with a given name, potentially aggregating all results + * from the searched classloaders. If no results are found, the resource name + * is prepended by '/' and tried again. + * + * This method will try to load the resources using the following methods (in order): + *

    + *
  • From Thread.currentThread().getContextClassLoader() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • callingClass.getClassLoader() + *
+ * + * @param resourceName The name of the resources to load + * @param callingClass The Class object of the calling object + */ + public static Iterator getResources(String resourceName, Class callingClass, boolean aggregate) throws IOException { + + AggregateIterator iterator = new AggregateIterator(); + + iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName)); + + if (!iterator.hasNext() || aggregate) { + iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName)); + } + + if (!iterator.hasNext() || aggregate) { + ClassLoader cl = callingClass.getClassLoader(); + + if (cl != null) { + iterator.addEnumeration(cl.getResources(resourceName)); + } + } + + if (!iterator.hasNext() && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) { + return getResources('/' + resourceName, callingClass, aggregate); + } + + return iterator; + } + + /** + * Load a given resource. + * + * This method will try to load the resource using the following methods (in order): + *
    + *
  • From Thread.currentThread().getContextClassLoader() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • callingClass.getClassLoader() + *
+ * + * @param resourceName The name IllegalStateException("Unable to call ")of the resource to load + * @param callingClass The Class object of the calling object + */ + public static URL getResource(String resourceName, Class callingClass) { + URL url = Thread.currentThread().getContextClassLoader().getResource(resourceName); + + if (url == null) { + url = ClassLoaderUtil.class.getClassLoader().getResource(resourceName); + } + + if (url == null) { + ClassLoader cl = callingClass.getClassLoader(); + + if (cl != null) { + url = cl.getResource(resourceName); + } + } + + if ((url == null) && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) { + return getResource('/' + resourceName, callingClass); + } + + return url; + } + + /** + * This is a convenience method to load a resource as a stream. + * + * The algorithm used to find the resource is given in getResource() + * + * @param resourceName The name of the resource to load + * @param callingClass The Class object of the calling object + */ + public static InputStream getResourceAsStream(String resourceName, Class callingClass) { + URL url = getResource(resourceName, callingClass); + + try { + return (url != null) ? url.openStream() : null; + } catch (IOException e) { + return null; + } + } + + /** + * Load a class with a given name. + * + * It will try to load the class in the following order: + *
    + *
  • From Thread.currentThread().getContextClassLoader() + *
  • Using the basic Class.forName() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • From the callingClass.getClassLoader() + *
+ * + * @param className The name of the class to load + * @param callingClass The Class object of the calling object + * @throws ClassNotFoundException If the class cannot be found anywhere. + */ + public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException { + try { + return Thread.currentThread().getContextClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + try { + return ClassLoaderUtil.class.getClassLoader().loadClass(className); + } catch (ClassNotFoundException exc) { + return callingClass.getClassLoader().loadClass(className); + } + } + } + } + + /** + * Aggregates Enumeration instances into one iterator and filters out duplicates. Always keeps one + * ahead of the enumerator to protect against returning duplicates. + */ + static class AggregateIterator implements Iterator { + + LinkedList> enums = new LinkedList>(); + Enumeration cur = null; + E next = null; + Set loaded = new HashSet(); + + public AggregateIterator addEnumeration(Enumeration e) { + if (e.hasMoreElements()) { + if (cur == null) { + cur = e; + next = e.nextElement(); + loaded.add(next); + } else { + enums.add(e); + } + } + return this; + } + + public boolean hasNext() { + return (next != null); + } + + public E next() { + if (next != null) { + E prev = next; + next = loadNext(); + return prev; + } else { + throw new NoSuchElementException(); + } + } + + private Enumeration determineCurrentEnumeration() { + if (cur != null && !cur.hasMoreElements()) { + if (enums.size() > 0) { + cur = enums.removeLast(); + } else { + cur = null; + } + } + return cur; + } + + private E loadNext() { + if (determineCurrentEnumeration() != null) { + E tmp = cur.nextElement(); + int loadedSize = loaded.size(); + while (loaded.contains(tmp)) { + tmp = loadNext(); + if (tmp == null || loaded.size() > loadedSize) { + break; + } + } + if (tmp != null) { + loaded.add(tmp); + } + return tmp; + } + return null; + + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClassPathFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClassPathFinder.java new file mode 100644 index 000000000..a8ebca2f4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClassPathFinder.java @@ -0,0 +1,177 @@ +/* + * $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.util; + +import com.opensymphony.xwork2.XWorkException; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Vector; + +/** + * This class is an utility class that will search through the classpath + * for files whose names match the given pattern. The filename is tested + * using the given implementation of {@link com.opensymphony.xwork2.util.PatternMatcher} by default it + * uses {@link com.opensymphony.xwork2.util.WildcardHelper} + * + * @version $Rev$ $Date$ + */ +public class ClassPathFinder { + + /** + * The String pattern to test against. + */ + private String pattern ; + + private int[] compiledPattern ; + + /** + * The PatternMatcher implementation to use + */ + private PatternMatcher patternMatcher = new WildcardHelper(); + + private Vector compared = new Vector(); + + /** + * retrieves the pattern in use + */ + public String getPattern() { + return pattern; + } + + /** + * sets the String pattern for comparing filenames + * @param pattern + */ + public void setPattern(String pattern) { + this.pattern = pattern; + } + + /** + * Builds a {@link java.util.Vector} containing Strings which each name a file + * who's name matches the pattern set by setPattern(String). The classpath is + * searched recursively, so use with caution. + * + * @return Vector containing matching filenames + */ + public Vector findMatches() { + Vector matches = new Vector(); + URLClassLoader cl = getURLClassLoader(); + if (cl == null ) { + throw new XWorkException("unable to attain an URLClassLoader") ; + } + URL[] parentUrls = cl.getURLs(); + compiledPattern = (int[]) patternMatcher.compilePattern(pattern); + for (URL url : parentUrls) { + if (!"file".equals(url.getProtocol())) { + continue ; + } + URI entryURI ; + try { + entryURI = url.toURI(); + } catch (URISyntaxException e) { + continue; + } + File entry = new File(entryURI) ; + Vector results = checkEntries(entry.list(), entry, ""); + if (results != null ) { + matches.addAll(results); + } + } + return matches; + } + + private Vector checkEntries(String[] entries, File parent, String prefix) { + + if (entries == null ) { + return null; + } + + Vector matches = new Vector(); + for (String listEntry : entries) { + File tempFile ; + if (!"".equals(prefix) ) { + tempFile = new File(parent, prefix + "/" + listEntry); + } + else { + tempFile = new File(parent, listEntry); + } + if (tempFile.isDirectory() && + !(".".equals(listEntry) || "..".equals(listEntry)) ) { + if (!"".equals(prefix) ) { + matches.addAll(checkEntries(tempFile.list(), parent, prefix + "/" + listEntry)); + } + else { + matches.addAll(checkEntries(tempFile.list(), parent, listEntry)); + } + } + else { + + String entryToCheck ; + if ("".equals(prefix)) { + entryToCheck = listEntry ; + } + else { + entryToCheck = prefix + "/" + listEntry ; + } + + if (compared.contains(entryToCheck) ) { + continue; + } + else { + compared.add(entryToCheck) ; + } + + boolean doesMatch = patternMatcher.match(new HashMap(), entryToCheck, compiledPattern); + if (doesMatch) { + matches.add(entryToCheck); + } + } + } + return matches ; + } + + /** + * sets the PatternMatcher implementation to use when comparing filenames + * @param patternMatcher + */ + public void setPatternMatcher(PatternMatcher patternMatcher) { + this.patternMatcher = patternMatcher; + } + + private URLClassLoader getURLClassLoader() { + URLClassLoader ucl = null; + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + + if(! (loader instanceof URLClassLoader)) { + loader = ClassPathFinder.class.getClassLoader(); + if (loader instanceof URLClassLoader) { + ucl = (URLClassLoader) loader ; + } + } + else { + ucl = (URLClassLoader) loader; + } + + return ucl ; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClearableValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClearableValueStack.java new file mode 100644 index 000000000..e4d129e94 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ClearableValueStack.java @@ -0,0 +1,29 @@ +/* + * $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.util; + +/** + * ValueStacks implementing this interface provide a way to remove values from + * their contexts. + */ +public interface ClearableValueStack { + /** + * Remove all values from the context + */ + void clearContextValues(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/CompoundRoot.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/CompoundRoot.java new file mode 100644 index 000000000..9abade066 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/CompoundRoot.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.util; + +import java.util.ArrayList; +import java.util.List; + + +/** + * A Stack that is implemented using a List. + * + * @author plightbo + * @version $Revision$ + */ +public class CompoundRoot extends ArrayList { + + public CompoundRoot() { + } + + public CompoundRoot(List list) { + super(list); + } + + + public CompoundRoot cutStack(int index) { + return new CompoundRoot(subList(index, size())); + } + + public Object peek() { + return get(0); + } + + public Object pop() { + return remove(0); + } + + public void push(Object o) { + add(0, o); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/CreateIfNull.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/CreateIfNull.java new file mode 100644 index 000000000..050ec2a55 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/CreateIfNull.java @@ -0,0 +1,77 @@ +/* + * 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.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

Sets the CreateIfNull for type conversion. + * + * + *

Annotation usage: + * + * + *

The CreateIfNull annotation must be applied at field or method level. + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterRequiredDefaultDescription
valuenofalseThe CreateIfNull property value.
+ * + * + *

Example code: + *

+ * 
+ * @CreateIfNull( value = true )
+ * private List users;
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface CreateIfNull { + + /** + * The CreateIfNull value. + * Defaults to true. + */ + boolean value() default true; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java new file mode 100644 index 000000000..d86714802 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java @@ -0,0 +1,366 @@ +/* + * Copyright 1999-2005 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.util; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.util.location.Location; +import com.opensymphony.xwork2.util.location.LocationAttributes; +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.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import java.util.Map; + +/** + * Helper class to create and retrieve information from location-enabled + * DOM-trees. + * + * @since 1.2 + */ +public class DomHelper { + + private static final Logger LOG = LoggerFactory.getLogger(DomHelper.class); + + public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; + + public static Location getLocationObject(Element element) { + return LocationAttributes.getLocation(element); + } + + + /** + * Creates a W3C Document that remembers the location of each element in + * the source file. The location of element nodes can then be retrieved + * using the {@link #getLocationObject(Element)} method. + * + * @param inputSource the inputSource to read the document from + */ + public static Document parse(InputSource inputSource) { + return parse(inputSource, null); + } + + + /** + * Creates a W3C Document that remembers the location of each element in + * the source file. The location of element nodes can then be retrieved + * using the {@link #getLocationObject(Element)} method. + * + * @param inputSource the inputSource to read the document from + * @param dtdMappings a map of DTD names and public ids + */ + public static Document parse(InputSource inputSource, Map dtdMappings) { + + SAXParserFactory factory = null; + String parserProp = System.getProperty("xwork.saxParserFactory"); + if (parserProp != null) { + try { + Class clazz = ObjectFactory.getObjectFactory().getClassInstance(parserProp); + factory = (SAXParserFactory) clazz.newInstance(); + } + catch (ClassNotFoundException e) { + LOG.error("Unable to load saxParserFactory set by system property 'xwork.saxParserFactory': " + parserProp, e); + } + catch (Exception e) { + LOG.error("Unable to load saxParserFactory set by system property 'xwork.saxParserFactory': " + parserProp, e); + } + } + + if (factory == null) { + factory = SAXParserFactory.newInstance(); + } + + factory.setValidating((dtdMappings != null)); + factory.setNamespaceAware(true); + + SAXParser parser = null; + try { + parser = factory.newSAXParser(); + } catch (Exception ex) { + throw new XWorkException("Unable to create SAX parser", ex); + } + + + DOMBuilder builder = new DOMBuilder(); + + // Enhance the sax stream with location information + ContentHandler locationHandler = new LocationAttributes.Pipe(builder); + + try { + parser.parse(inputSource, new StartHandler(locationHandler, dtdMappings)); + } catch (Exception ex) { + throw new XWorkException(ex); + } + + return builder.getDocument(); + } + + /** + * The DOMBuilder is a utility class that will generate a W3C + * DOM Document from SAX events. + * + * @author Carsten Ziegeler + */ + static public class DOMBuilder implements ContentHandler { + + /** The default transformer factory shared by all instances */ + protected static SAXTransformerFactory FACTORY; + + /** The transformer factory */ + protected SAXTransformerFactory factory; + + /** The result */ + protected DOMResult result; + + /** The parentNode */ + protected Node parentNode; + + protected ContentHandler nextHandler; + + static { + String parserProp = System.getProperty("xwork.saxTransformerFactory"); + if (parserProp != null) { + try { + Class clazz = ObjectFactory.getObjectFactory().getClassInstance(parserProp); + FACTORY = (SAXTransformerFactory) clazz.newInstance(); + } + catch (ClassNotFoundException e) { + LOG.error("Unable to load SAXTransformerFactory set by system property 'xwork.saxTransformerFactory': " + parserProp, e); + } + catch (Exception e) { + LOG.error("Unable to load SAXTransformerFactory set by system property 'xwork.saxTransformerFactory': " + parserProp, e); + } + } + + if (FACTORY == null) { + FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance(); + } + } + + /** + * Construct a new instance of this DOMBuilder. + */ + public DOMBuilder() { + this((Node) null); + } + + /** + * Construct a new instance of this DOMBuilder. + */ + public DOMBuilder(SAXTransformerFactory factory) { + this(factory, null); + } + + /** + * Constructs a new instance that appends nodes to the given parent node. + */ + public DOMBuilder(Node parentNode) { + this(null, parentNode); + } + + /** + * Construct a new instance of this DOMBuilder. + */ + public DOMBuilder(SAXTransformerFactory factory, Node parentNode) { + this.factory = factory == null? FACTORY: factory; + this.parentNode = parentNode; + setup(); + } + + /** + * Setup this instance transformer and result objects. + */ + private void setup() { + try { + TransformerHandler handler = this.factory.newTransformerHandler(); + nextHandler = handler; + if (this.parentNode != null) { + this.result = new DOMResult(this.parentNode); + } else { + this.result = new DOMResult(); + } + handler.setResult(this.result); + } catch (javax.xml.transform.TransformerException local) { + throw new XWorkException("Fatal-Error: Unable to get transformer handler", local); + } + } + + /** + * Return the newly built Document. + */ + public Document getDocument() { + if (this.result == null || this.result.getNode() == null) { + return null; + } else if (this.result.getNode().getNodeType() == Node.DOCUMENT_NODE) { + return (Document) this.result.getNode(); + } else { + return this.result.getNode().getOwnerDocument(); + } + } + + public void setDocumentLocator(Locator locator) { + nextHandler.setDocumentLocator(locator); + } + + public void startDocument() throws SAXException { + nextHandler.startDocument(); + } + + public void endDocument() throws SAXException { + nextHandler.endDocument(); + } + + public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { + nextHandler.startElement(uri, loc, raw, attrs); + } + + public void endElement(String arg0, String arg1, String arg2) throws SAXException { + nextHandler.endElement(arg0, arg1, arg2); + } + + public void startPrefixMapping(String arg0, String arg1) throws SAXException { + nextHandler.startPrefixMapping(arg0, arg1); + } + + public void endPrefixMapping(String arg0) throws SAXException { + nextHandler.endPrefixMapping(arg0); + } + + public void characters(char[] arg0, int arg1, int arg2) throws SAXException { + nextHandler.characters(arg0, arg1, arg2); + } + + public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { + nextHandler.ignorableWhitespace(arg0, arg1, arg2); + } + + public void processingInstruction(String arg0, String arg1) throws SAXException { + nextHandler.processingInstruction(arg0, arg1); + } + + public void skippedEntity(String arg0) throws SAXException { + nextHandler.skippedEntity(arg0); + } + } + + public static class StartHandler extends DefaultHandler { + + private ContentHandler nextHandler; + private Map dtdMappings; + + /** + * Create a filter that is chained to another handler. + * @param next the next handler in the chain. + */ + public StartHandler(ContentHandler next, Map dtdMappings) { + nextHandler = next; + this.dtdMappings = dtdMappings; + } + + @Override + public void setDocumentLocator(Locator locator) { + nextHandler.setDocumentLocator(locator); + } + + @Override + public void startDocument() throws SAXException { + nextHandler.startDocument(); + } + + @Override + public void endDocument() throws SAXException { + nextHandler.endDocument(); + } + + @Override + public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { + nextHandler.startElement(uri, loc, raw, attrs); + } + + @Override + public void endElement(String arg0, String arg1, String arg2) throws SAXException { + nextHandler.endElement(arg0, arg1, arg2); + } + + @Override + public void startPrefixMapping(String arg0, String arg1) throws SAXException { + nextHandler.startPrefixMapping(arg0, arg1); + } + + @Override + public void endPrefixMapping(String arg0) throws SAXException { + nextHandler.endPrefixMapping(arg0); + } + + @Override + public void characters(char[] arg0, int arg1, int arg2) throws SAXException { + nextHandler.characters(arg0, arg1, arg2); + } + + @Override + public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { + nextHandler.ignorableWhitespace(arg0, arg1, arg2); + } + + @Override + public void processingInstruction(String arg0, String arg1) throws SAXException { + nextHandler.processingInstruction(arg0, arg1); + } + + @Override + public void skippedEntity(String arg0) throws SAXException { + nextHandler.skippedEntity(arg0); + } + + @Override + public InputSource resolveEntity(String publicId, String systemId) { + if (dtdMappings != null && dtdMappings.containsKey(publicId)) { + String val = dtdMappings.get(publicId).toString(); + return new InputSource(ClassLoaderUtil.getResourceAsStream(val, DomHelper.class)); + } + return null; + } + + @Override + public void warning(SAXParseException exception) { + } + + @Override + public void error(SAXParseException exception) throws SAXException { + LOG.error(exception.getMessage() + " at (" + exception.getPublicId() + ":" + + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")", exception); + throw exception; + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + LOG.fatal(exception.getMessage() + " at (" + exception.getPublicId() + ":" + + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")", exception); + throw exception; + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/Element.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/Element.java new file mode 100644 index 000000000..30903d2d7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/Element.java @@ -0,0 +1,81 @@ +/* + * 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.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

Sets the Element for type conversion. + * + * + *

Annotation usage: + * + * + *

The Element annotation must be applied at field or method level. + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterRequiredDefaultDescription
valuenojava.lang.Object.classThe element property value.
+ * + * + *

Example code: + *

+ * 
+ * // The key property for User objects within the users collection is the userName attribute.
+ * @Element( value = com.acme.User )
+ * private Map userMap;
+ *
+ * @Element( value = com.acme.User )
+ * public List userList;
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface Element { + + /** + * The Element value. + * Defaults to java.lang.Object.class. + */ + Class value() default java.lang.Object.class; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/FileManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/FileManager.java new file mode 100644 index 000000000..abc36f78a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/FileManager.java @@ -0,0 +1,321 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +/** + * FileManager + *

+ * This class was brought in from oscore trunk revision 147. + * + * @author Jason Carreira + * Created May 7, 2003 8:44:26 PM + */ +public class FileManager { + //~ Static fields/initializers ///////////////////////////////////////////// + + private static Logger LOG = LoggerFactory.getLogger(FileManager.class); + + private static Map files = Collections.synchronizedMap(new HashMap()); + protected static boolean reloadingConfigs = true; + + private static final String JAR_FILE_NAME_SEPARATOR = "!/"; + private static final String JAR_FILE_EXTENSION_END = ".jar/"; + + + //~ Constructors /////////////////////////////////////////////////////////// + + private FileManager() { + } + + //~ Methods //////////////////////////////////////////////////////////////// + + public static void setReloadingConfigs(boolean reloadingConfigs) { + FileManager.reloadingConfigs = reloadingConfigs; + } + + public static boolean isReloadingConfigs() { + return reloadingConfigs; + } + + public static boolean fileNeedsReloading(String fileName, Class clazz) { + URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz); + return fileUrl != null && fileNeedsReloading(fileUrl.toString()); + } + + public static boolean fileNeedsReloading(String fileName) { + Revision revision = files.get(fileName); + + if (revision == null) { + // no revision yet and we keep the revision history, so + // return whether the file needs to be loaded for the first time + return reloadingConfigs; + } + + return revision.needsReloading(); + } + + /** + * Loads opens the named file and returns the InputStream + * + * @param fileName - the name of the file to open + * @return an InputStream of the file contents or null + * @throws IllegalArgumentException if there is no file with the given file name + */ + public static InputStream loadFile(String fileName, Class clazz) { + URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz); + return loadFile(fileUrl); + } + + /** + * Loads opens the named file and returns the InputStream + * + * @param fileUrl - the URL of the file to open + * @return an InputStream of the file contents or null + * @throws IllegalArgumentException if there is no file with the given file name + */ + public static InputStream loadFile(URL fileUrl) { + return loadFile(fileUrl, true); + } + + /** + * Loads opens the named file and returns the InputStream + * + * @param fileUrl - the URL of the file to open + * @param openStream - if true, open an InputStream to the file and return it + * @return an InputStream of the file contents or null + * @throws IllegalArgumentException if there is no file with the given file name + */ + public static InputStream loadFile(URL fileUrl, boolean openStream) { + if (fileUrl == null) { + return null; + } + + String fileName = fileUrl.toString(); + InputStream is = null; + + if (openStream) { + try { + is = fileUrl.openStream(); + + if (is == null) { + throw new IllegalArgumentException("No file '" + fileName + "' found as a resource"); + } + } catch (IOException e) { + throw new IllegalArgumentException("No file '" + fileName + "' found as a resource"); + } + } + + if (isReloadingConfigs()) { + Revision revision; + + if (LOG.isDebugEnabled()) { + LOG.debug("Creating revision for URL: " +fileName); + } + if (URLUtil.isJBoss5Url(fileUrl)) { + revision = JBossFileRevision.build(fileUrl); + } else if (URLUtil.isJarURL(fileUrl)) { + revision = JarEntryRevision.build(fileUrl); + } else { + revision = FileRevision.build(fileUrl); + } + if (revision == null) { + files.put(fileName, Revision.build(fileUrl)); + } else { + files.put(fileName, revision); + } + } + return is; + } + + //~ Inner Classes ////////////////////////////////////////////////////////// + + /** + * Class represents common revsion resource, should be used as default class when no other option exists + */ + private static class Revision { + + public Revision() { + } + + public boolean needsReloading() { + return false; + } + + public static Revision build(URL fileUrl) { + return new Revision(); + } + } + + /** + * Represents file resource revision, used for file://* resources + */ + private static class FileRevision extends Revision { + private File file; + private long lastModified; + + public FileRevision(File file, long lastUpdated) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + this.file = file; + this.lastModified = lastUpdated; + } + + public File getFile() { + return file; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + + public long getLastModified() { + return lastModified; + } + + public boolean needsReloading() { + return this.lastModified < this.file.lastModified(); + } + + public static Revision build(URL fileUrl) { + File file; + try { + file = new File(fileUrl.toURI()); + } catch (URISyntaxException e) { + file = new File(fileUrl.getPath()); + } catch (Throwable t) { + return null; + } + if (file.exists() && file.canRead()) { + long lastModified = file.lastModified(); + return new FileRevision(file, lastModified); + } + return null; + } + } + + /** + * Represents file resource revision, used for vfszip://* resources + */ + private static class JBossFileRevision extends FileRevision { + + public JBossFileRevision(File file, long lastUpdated) { + super(file, lastUpdated); + } + + public static Revision build(URL fileUrl) { + File file; + URL url = URLUtil.normalizeToFileProtocol(fileUrl); + try { + if (url != null) { + file = new File(url.toURI()); + } else { + return null; + } + } catch (URISyntaxException e) { + file = new File(url.getPath()); + } + if (file.exists() && file.canRead()) { + long lastModified = file.lastModified(); + return new FileRevision(file, lastModified); + } + return null; + } + } + + /** + * Represents jar resurce revision, used for jar://* resource + */ + private static class JarEntryRevision extends Revision { + + private String jarFileName; + private String fileNameInJar; + private long lastModified; + + public JarEntryRevision(String jarFileName, String fileNameInJar, long lastModified) { + if ((jarFileName == null) || (fileNameInJar == null)) { + throw new IllegalArgumentException("JarFileName and FileNameInJar cannot be null"); + } + this.jarFileName = jarFileName; + this.fileNameInJar = fileNameInJar; + this.lastModified = lastModified; + } + + public boolean needsReloading() { + ZipEntry entry; + try { + JarFile jarFile = new JarFile(this.jarFileName); + entry = jarFile.getEntry(this.fileNameInJar); + } + catch (IOException e) { + entry = null; + } + + if (entry != null) { + return (this.lastModified < entry.getTime()); + } else { + return false; + } + } + + public static Revision build(URL fileUrl) { + // File within a Jar + // Find separator index of jar filename and filename within jar + try { + String fileName = fileUrl.toString(); + int separatorIndex = fileName.indexOf(JAR_FILE_NAME_SEPARATOR); + if (separatorIndex == -1) { + separatorIndex = fileName.lastIndexOf(JAR_FILE_EXTENSION_END); + } + if (separatorIndex == -1) { + LOG.warn("Could not find end of jar file!"); + return null; + } + // Split file name + String jarFileName = fileName.substring(0, separatorIndex); + String fileNameInJar = fileName.substring(separatorIndex + JAR_FILE_NAME_SEPARATOR.length()).replaceAll("%20", " "); + + URL url = URLUtil.normalizeToFileProtocol(fileUrl); + if (url != null) { + JarFile jarFile = new JarFile(new File(url.getPath().replaceAll("%20", " "))); + ZipEntry entry = jarFile.getEntry(fileNameInJar); + return new JarEntryRevision(jarFileName.toString(), fileNameInJar, entry.getTime()); + } else { + return null; + } + } catch (Throwable e) { + LOG.warn("Could not create JarEntryRevision!", e); + return null; + } + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/Key.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/Key.java new file mode 100644 index 000000000..c1b0fc8bf --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/Key.java @@ -0,0 +1,78 @@ +/* + * 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.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

Sets the Key for type conversion. + * + * + *

Annotation usage: + * + * + *

The Key annotation must be applied at field or method level. + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterRequiredDefaultDescription
valuenojava.lang.Object.classThe key property value.
+ * + * + *

Example code: + *

+ * 
+ * // The key property for User objects within the users collection is the userName attribute.
+ * @Key( value = java.lang.Long.class )
+ * private Map userMap;
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface Key { + + /** + * The Key value. + * Defaults to java.lang.Object.class. + */ + Class value() default java.lang.Object.class; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/KeyProperty.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/KeyProperty.java new file mode 100644 index 000000000..8832beebf --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/KeyProperty.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

Sets the KeyProperty for type conversion. + * + * + *

Annotation usage: + * + * + *

The KeyProperty annotation must be applied at field or method level. + *

This annotation should be used with Generic types, if the key property of the key element needs to be specified. + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ParameterRequiredDefaultDescription
valuenoidThe key property value.
+ * + * + *

Example code: + *

+ * 
+ * // The key property for User objects within the users collection is the userName attribute.
+ * @KeyProperty( value = "userName" )
+ * protected List users = null;
+ * 
+ * 
+ * + * @author Patrick Lightbody + * @author Rainer Hermanns + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface KeyProperty { + + /** + * The KeyProperty value. + * Defaults to the id attribute. + */ + String value() default "id"; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java new file mode 100644 index 000000000..e52487ab9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/LocalizedTextUtil.java @@ -0,0 +1,950 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ModelDriven; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + + +/** + * Provides support for localization in XWork. + *

+ * + * Resource bundles are searched in the following order:

+ *

+ *

    + *
  1. ActionClass.properties
  2. + *
  3. Interface.properties (every interface and sub-interface)
  4. + *
  5. BaseClass.properties (all the way to Object.properties)
  6. + *
  7. ModelDriven's model (if implements ModelDriven), for the model object repeat from 1
  8. + *
  9. package.properties (of the directory where class is located and every parent directory all the way to the root directory)
  10. + *
  11. search up the i18n message key hierarchy itself
  12. + *
  13. global resource properties
  14. + *
+ *

+ * + *

+ * + * To clarify #5, while traversing the package hierarchy, Struts 2 will look for a file package.properties:

+ * com/
+ *   acme/
+ *     package.properties
+ *     actions/
+ *       package.properties
+ *       FooAction.java
+ *       FooAction.properties
+ *

+ * If FooAction.properties does not exist, com/acme/action/package.properties will be searched for, if + * not found com/acme/package.properties, if not found com/package.properties, etc. + *

+ * + *

+ * + * A global resource bundle could be specified programatically, as well as the locale. + *

+ * + * + * @author Jason Carreira + * @author Mark Woon + * @author Rainer Hermanns + * @author tm_jee + * @version $Date$ $Id$ + */ +public class LocalizedTextUtil { + + private static final List DEFAULT_RESOURCE_BUNDLES = new CopyOnWriteArrayList(); + private static final Logger LOG = LoggerFactory.getLogger(LocalizedTextUtil.class); + private static boolean reloadBundles = false; + private static final ResourceBundle EMPTY_BUNDLE = new EmptyResourceBundle(); + private static final ConcurrentMap bundlesMap = new ConcurrentHashMap(); + private static final Map messageFormats = new HashMap(); + + private static ClassLoader delegatedClassLoader; + + static { + clearDefaultResourceBundles(); + } + + + /** + * Clears the internal list of resource bundles. + */ + public static void clearDefaultResourceBundles() { + if (DEFAULT_RESOURCE_BUNDLES != null) { + synchronized (DEFAULT_RESOURCE_BUNDLES) { + DEFAULT_RESOURCE_BUNDLES.clear(); + DEFAULT_RESOURCE_BUNDLES.add("com/opensymphony/xwork2/xwork-messages"); + } + } else { + synchronized (DEFAULT_RESOURCE_BUNDLES) { + DEFAULT_RESOURCE_BUNDLES.add("com/opensymphony/xwork2/xwork-messages"); + } + } + } + + /** + * Should resorce bundles be reloaded. + * + * @param reloadBundles reload bundles? + */ + public static void setReloadBundles(boolean reloadBundles) { + LocalizedTextUtil.reloadBundles = reloadBundles; + } + + /** + * Add's the bundle to the internal list of default bundles. + *

+ * If the bundle already exists in the list it will be readded. + * + * @param resourceBundleName the name of the bundle to add. + */ + public static void addDefaultResourceBundle(String resourceBundleName) { + //make sure this doesn't get added more than once + synchronized (DEFAULT_RESOURCE_BUNDLES) { + DEFAULT_RESOURCE_BUNDLES.remove(resourceBundleName); + DEFAULT_RESOURCE_BUNDLES.add(0, resourceBundleName); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Added default resource bundle '" + resourceBundleName + "' to default resource bundles = " + DEFAULT_RESOURCE_BUNDLES); + } + } + + /** + * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale + * with language "en", country "US" and variant "foo". This will parse the output of + * {@link java.util.Locale#toString()}. + * + * @param localeStr The locale String to parse. + * @param defaultLocale The locale to use if localeStr is null. + * @return requested Locale + */ + public static Locale localeFromString(String localeStr, Locale defaultLocale) { + if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) { + if (defaultLocale != null) { + return defaultLocale; + } + return Locale.getDefault(); + } + + int index = localeStr.indexOf('_'); + if (index < 0) { + return new Locale(localeStr); + } + + String language = localeStr.substring(0, index); + if (index == localeStr.length()) { + return new Locale(language); + } + + localeStr = localeStr.substring(index + 1); + index = localeStr.indexOf('_'); + if (index < 0) { + return new Locale(language, localeStr); + } + + String country = localeStr.substring(0, index); + if (index == localeStr.length()) { + return new Locale(language, country); + } + + localeStr = localeStr.substring(index + 1); + return new Locale(language, country, localeStr); + } + + /** + * Returns a localized message for the specified key, aTextName. Neither the key nor the + * message is evaluated. + * + * @param aTextName the message key + * @param locale the locale the message should be for + * @return a localized message based on the specified key, or null if no localized message can be found for it + */ + public static String findDefaultText(String aTextName, Locale locale) { + List localList = DEFAULT_RESOURCE_BUNDLES; + + for (String bundleName : localList) { + ResourceBundle bundle = findResourceBundle(bundleName, locale); + if (bundle != null) { + reloadBundles(); + try { + return bundle.getString(aTextName); + } catch (MissingResourceException e) { + // ignore and try others + } + } + } + + return null; + } + + /** + * Returns a localized message for the specified key, aTextName, substituting variables from the + * array of params into the message. Neither the key nor the message is evaluated. + * + * @param aTextName the message key + * @param locale the locale the message should be for + * @param params an array of objects to be substituted into the message text + * @return A formatted message based on the specified key, or null if no localized message can be found for it + */ + public static String findDefaultText(String aTextName, Locale locale, Object[] params) { + String defaultText = findDefaultText(aTextName, locale); + if (defaultText != null) { + MessageFormat mf = buildMessageFormat(defaultText, locale); + return mf.format(params); + } + return null; + } + + /** + * Finds the given resorce bundle by it's name. + *

+ * Will use Thread.currentThread().getContextClassLoader() as the classloader. + * If {@link #delegatedClassLoader} is defined and the bundle cannot be found the current + * classloader it will delegate to that. + * + * @param aBundleName the name of the bundle (usually it's FQN classname). + * @param locale the locale. + * @return the bundle, null if not found. + */ + public static ResourceBundle findResourceBundle(String aBundleName, Locale locale) { + String key = createMissesKey(aBundleName, locale); + + ResourceBundle bundle; + + try { + if (!bundlesMap.containsKey(key)) { + bundle = ResourceBundle.getBundle( + aBundleName, + locale, + Thread.currentThread().getContextClassLoader()); + bundlesMap.put(key, bundle); + } + + bundle = bundlesMap.get(key); + } catch (MissingResourceException ex) { + if ( delegatedClassLoader != null) { + try { + if (!bundlesMap.containsKey(key)) { + bundle = ResourceBundle.getBundle( + aBundleName, + locale, + delegatedClassLoader); + bundlesMap.put(key, bundle); + } + + bundle = bundlesMap.get(key); + + } catch (MissingResourceException e) { + bundle = EMPTY_BUNDLE; + bundlesMap.put(key, bundle); + } + } else { + bundle = EMPTY_BUNDLE; + bundlesMap.put(key, bundle); + } + } + return (bundle == EMPTY_BUNDLE) ? null : bundle; + } + + /** + * Sets a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader + * + * @param classLoader + */ + public static void setDelegatedClassLoader(final ClassLoader classLoader) { + synchronized (bundlesMap) { + delegatedClassLoader = classLoader; + } + } + + /** + * Removes the bundle from any cached "misses" + * + * @param bundleName + */ + public static void clearBundle(final String bundleName) { + synchronized (bundlesMap) { + bundlesMap.remove(bundleName); + } + } + + + /** + * Creates a key to used for lookup/storing in the bundle misses cache. + * + * @param aBundleName the name of the bundle (usually it's FQN classname). + * @param locale the locale. + * @return the key to use for lookup/storing in the bundle misses cache. + */ + private static String createMissesKey + (String + aBundleName, Locale + locale) { + return aBundleName + "_" + locale.toString(); + } + + /** + * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)} + * with aTextName as the default message. + * + * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) + */ + public static String findText + (Class + aClass, String + aTextName, Locale + locale) { + return findText(aClass, aTextName, locale, aTextName, new Object[0]); + } + + /** + * Finds a localized text message for the given key, aTextName. Both the key and the message + * itself is evaluated as required. The following algorithm is used to find the requested + * message: + *

+ *

    + *
  1. Look for message in aClass' class hierarchy. + *
      + *
    1. Look for the message in a resource bundle for aClass
    2. + *
    3. If not found, look for the message in a resource bundle for any implemented interface
    4. + *
    5. If not found, traverse up the Class' hierarchy and repeat from the first sub-step
    6. + *
  2. + *
  3. If not found and aClass is a {@link ModelDriven} Action, then look for message in + * the model's class hierarchy (repeat sub-steps listed above).
  4. + *
  5. If not found, look for message in child property. This is determined by evaluating + * the message key as an OGNL expression. For example, if the key is + * user.address.state, then it will attempt to see if "user" can be resolved into an + * object. If so, repeat the entire process fromthe beginning with the object's class as + * aClass and "address.state" as the message key.
  6. + *
  7. If not found, look for the message in aClass' package hierarchy.
  8. + *
  9. If still not found, look for the message in the default resource bundles.
  10. + *
  11. Return defaultMessage
  12. + *
+ *

+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a + * message for that specific key cannot be found, the general form will also be looked up + * (i.e. user.phone[*]). + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + * + * @param aClass the class whose name to use as the start point for the search + * @param aTextName the key to find the text message for + * @param locale the locale the message should be for + * @param defaultMessage the message to be returned if no text message can be found in any + * resource bundle + * @return the localized text, or null if none can be found and no defaultMessage is provided + */ + public static String findText + (Class + aClass, String + aTextName, Locale + locale, String + defaultMessage, Object[] args) { + ValueStack valueStack = ActionContext.getContext().getValueStack(); + return findText(aClass, aTextName, locale, defaultMessage, args, valueStack); + + } + + /** + * Finds a localized text message for the given key, aTextName. Both the key and the message + * itself is evaluated as required. The following algorithm is used to find the requested + * message: + *

+ *

    + *
  1. Look for message in aClass' class hierarchy. + *
      + *
    1. Look for the message in a resource bundle for aClass
    2. + *
    3. If not found, look for the message in a resource bundle for any implemented interface
    4. + *
    5. If not found, traverse up the Class' hierarchy and repeat from the first sub-step
    6. + *
  2. + *
  3. If not found and aClass is a {@link ModelDriven} Action, then look for message in + * the model's class hierarchy (repeat sub-steps listed above).
  4. + *
  5. If not found, look for message in child property. This is determined by evaluating + * the message key as an OGNL expression. For example, if the key is + * user.address.state, then it will attempt to see if "user" can be resolved into an + * object. If so, repeat the entire process fromthe beginning with the object's class as + * aClass and "address.state" as the message key.
  6. + *
  7. If not found, look for the message in aClass' package hierarchy.
  8. + *
  9. If still not found, look for the message in the default resource bundles.
  10. + *
  11. Return defaultMessage
  12. + *
+ *

+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a + * message for that specific key cannot be found, the general form will also be looked up + * (i.e. user.phone[*]). + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + *

+ * If a message is not found a WARN log will be logged. + * + * @param aClass the class whose name to use as the start point for the search + * @param aTextName the key to find the text message for + * @param locale the locale the message should be for + * @param defaultMessage the message to be returned if no text message can be found in any + * resource bundle + * @param valueStack the value stack to use to evaluate expressions instead of the + * one in the ActionContext ThreadLocal + * @return the localized text, or null if none can be found and no defaultMessage is provided + */ + public static String findText + (Class + aClass, String + aTextName, Locale + locale, String + defaultMessage, Object[] args, ValueStack + valueStack) { + String indexedTextName = null; + if (aTextName == null) { + LOG.warn("Trying to find text with null key!"); + aTextName = ""; + } + // calculate indexedTextName (collection[*]) if applicable + if (aTextName.contains("[")) { + int i = -1; + + indexedTextName = aTextName; + + while ((i = indexedTextName.indexOf("[", i + 1)) != -1) { + int j = indexedTextName.indexOf("]", i); + String a = indexedTextName.substring(0, i); + String b = indexedTextName.substring(j); + indexedTextName = a + "[*" + b; + } + } + + // search up class hierarchy + String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack); + + if (msg != null) { + return msg; + } + + if (ModelDriven.class.isAssignableFrom(aClass)) { + ActionContext context = ActionContext.getContext(); + // search up model's class hierarchy + ActionInvocation actionInvocation = context.getActionInvocation(); + + // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null + if (actionInvocation != null) { + Object action = actionInvocation.getAction(); + if (action instanceof ModelDriven) { + Object model = ((ModelDriven) action).getModel(); + if (model != null) { + msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack); + if (msg != null) { + return msg; + } + } + } + } + } + + // nothing still? alright, search the package hierarchy now + for (Class clazz = aClass; + (clazz != null) && !clazz.equals(Object.class); + clazz = clazz.getSuperclass()) { + + String basePackageName = clazz.getName(); + while (basePackageName.lastIndexOf('.') != -1) { + basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.')); + String packageName = basePackageName + ".package"; + msg = getMessage(packageName, locale, aTextName, valueStack, args); + + if (msg != null) { + return msg; + } + + if (indexedTextName != null) { + msg = getMessage(packageName, locale, indexedTextName, valueStack, args); + + if (msg != null) { + return msg; + } + } + } + } + + // see if it's a child property + int idx = aTextName.indexOf("."); + + if (idx != -1) { + String newKey = null; + String prop = null; + + if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) { + idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length()); + + if (idx != -1) { + prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx); + newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1); + } + } else { + prop = aTextName.substring(0, idx); + newKey = aTextName.substring(idx + 1); + } + + if (prop != null) { + Object obj = valueStack.findValue(prop); + try { + Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot()); + if (actionObj != null) { + PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop); + + if (propertyDescriptor != null) { + Class clazz = propertyDescriptor.getPropertyType(); + + if (clazz != null) { + if (obj != null) + valueStack.push(obj); + msg = findText(clazz, newKey, locale, null, args); + if (obj != null) + valueStack.pop(); + + if (msg != null) { + return msg; + } + } + } + } + } + catch (Exception e) { + LOG.debug("unable to find property " + prop, e); + } + } + } + + // get default + GetDefaultMessageReturnArg result = null; + if (indexedTextName == null) { + result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage); + } else { + result = getDefaultMessage(aTextName, locale, valueStack, args, null); + if (result != null && result.message != null) { + return result.message; + } + result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage); + } + + // could we find the text, if not log a warn + if (unableToFindTextForKey(result)) { + String warn = "Unable to find text for key '" + aTextName + "' "; + if (indexedTextName != null) { + warn += " or indexed key '" + indexedTextName + "' "; + } + warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'"; + LOG.debug(warn); + } + + return result != null ? result.message : null; + } + + /** + * Determines if we found the text in the bundles. + * + * @param result the result so far + * @return true if we could not find the text, false if the text was found (=success). + */ + private static boolean unableToFindTextForKey + (GetDefaultMessageReturnArg + result) { + if (result == null || result.message == null) { + return true; + } + + // did we find it in the bundle, then no problem? + if (result.foundInBundle) { + return false; + } + + // not found in bundle + return true; + } + + /** + * Finds a localized text message for the given key, aTextName, in the specified resource bundle + * with aTextName as the default message. + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + * + * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[]) + */ + public static String findText + (ResourceBundle + bundle, String + aTextName, Locale + locale) { + return findText(bundle, aTextName, locale, aTextName, new Object[0]); + } + + /** + * Finds a localized text message for the given key, aTextName, in the specified resource + * bundle. + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + *

+ * If a message is not found a WARN log will be logged. + * + * @param bundle the bundle + * @param aTextName the key + * @param locale the locale + * @param defaultMessage the default message to use if no message was found in the bundle + * @param args arguments for the message formatter. + */ + public static String findText + (ResourceBundle + bundle, String + aTextName, Locale + locale, String + defaultMessage, Object[] args) { + ValueStack valueStack = ActionContext.getContext().getValueStack(); + return findText(bundle, aTextName, locale, defaultMessage, args, valueStack); + } + + /** + * Finds a localized text message for the given key, aTextName, in the specified resource + * bundle. + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + *

+ * If a message is not found a WARN log will be logged. + * + * @param bundle the bundle + * @param aTextName the key + * @param locale the locale + * @param defaultMessage the default message to use if no message was found in the bundle + * @param args arguments for the message formatter. + * @param valueStack the OGNL value stack. + */ + public static String findText + (ResourceBundle + bundle, String + aTextName, Locale + locale, String + defaultMessage, Object[] args, ValueStack + valueStack) { + try { + reloadBundles(); + + String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack); + MessageFormat mf = buildMessageFormat(message, locale); + + return mf.format(args); + } catch (MissingResourceException ex) { + // ignore + } + + GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage); + if (unableToFindTextForKey(result)) { + LOG.warn("Unable to find text for key '" + aTextName + "' in ResourceBundles for locale '" + locale + "'"); + } + return result != null ? result.message : null; + } + + /** + * Gets the default message. + */ + private static GetDefaultMessageReturnArg getDefaultMessage + (String + key, Locale + locale, ValueStack + valueStack, Object[] args, String + defaultMessage) { + GetDefaultMessageReturnArg result = null; + boolean found = true; + + if (key != null) { + String message = findDefaultText(key, locale); + + if (message == null) { + message = defaultMessage; + found = false; // not found in bundles + } + + // defaultMessage may be null + if (message != null) { + MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale); + + String msg = mf.format(args); + result = new GetDefaultMessageReturnArg(msg, found); + } + } + + return result; + } + + /** + * Gets the message from the named resource bundle. + */ + private static String getMessage + (String + bundleName, Locale + locale, String + key, ValueStack + valueStack, Object[] args) { + ResourceBundle bundle = findResourceBundle(bundleName, locale); + if (bundle == null) { + return null; + } + + reloadBundles(); + + try { + String message = TextParseUtil.translateVariables(bundle.getString(key), valueStack); + MessageFormat mf = buildMessageFormat(message, locale); + return mf.format(args); + } catch (MissingResourceException e) { + return null; + } + } + + private static MessageFormat buildMessageFormat + (String + pattern, Locale + locale) { + MessageFormatKey key = new MessageFormatKey(pattern, locale); + MessageFormat format = null; + synchronized (messageFormats) { + format = (MessageFormat) messageFormats.get(key); + if (format == null) { + format = new MessageFormat(pattern); + format.setLocale(locale); + format.applyPattern(pattern); + messageFormats.put(key, format); + } + } + + return format; + } + + /** + * Traverse up class hierarchy looking for message. Looks at class, then implemented interface, + * before going up hierarchy. + */ + private static String findMessage + (Class + clazz, String + key, String + indexedKey, Locale + locale, Object[] args, Set + checked, ValueStack + valueStack) { + if (checked == null) { + checked = new TreeSet(); + } else if (checked.contains(clazz.getName())) { + return null; + } + + // look in properties of this class + String msg = getMessage(clazz.getName(), locale, key, valueStack, args); + + if (msg != null) { + return msg; + } + + if (indexedKey != null) { + msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args); + + if (msg != null) { + return msg; + } + } + + // look in properties of implemented interfaces + Class[] interfaces = clazz.getInterfaces(); + + for (Class anInterface : interfaces) { + msg = getMessage(anInterface.getName(), locale, key, valueStack, args); + + if (msg != null) { + return msg; + } + + if (indexedKey != null) { + msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args); + + if (msg != null) { + return msg; + } + } + } + + // traverse up hierarchy + if (clazz.isInterface()) { + interfaces = clazz.getInterfaces(); + + for (Class anInterface : interfaces) { + msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack); + + if (msg != null) { + return msg; + } + } + } else { + if (!clazz.equals(Object.class) && !clazz.isPrimitive()) { + return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack); + } + } + + return null; + } + + private static void reloadBundles() { + if (reloadBundles) { + try { + clearMap(ResourceBundle.class, null, "cacheList"); + + // now, for the true and utter hack, if we're running in tomcat, clear + // it's class loader resource cache as well. + clearTomcatCache(); + } + catch (Exception e) { + LOG.error("Could not reload resource bundles", e); + } + } + } + + + private static void clearTomcatCache() { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + // no need for compilation here. + Class cl = loader.getClass(); + + try { + if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) { + clearMap(cl, loader, "resourceEntries"); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("class loader " + cl.getName() + " is not tomcat loader."); + } + } + } + catch (Exception e) { + LOG.warn("couldn't clear tomcat cache", e); + } + } + + + private static void clearMap + (Class + cl, Object + obj, String + name) + throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, + InvocationTargetException { + Field field = cl.getDeclaredField(name); + field.setAccessible(true); + + Object cache = field.get(obj); + + synchronized (cache) { + Class ccl = cache.getClass(); + Method clearMethod = ccl.getMethod("clear"); + clearMethod.invoke(cache); + } + + } + + /** + * Clears all the internal lists. + */ + public static void reset + () { + clearDefaultResourceBundles(); + + bundlesMap.clear(); + + synchronized (messageFormats) { + messageFormats.clear(); + } + } + + static class MessageFormatKey { + String pattern; + Locale locale; + + MessageFormatKey(String pattern, Locale locale) { + this.pattern = pattern; + this.locale = locale; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MessageFormatKey)) return false; + + final MessageFormatKey messageFormatKey = (MessageFormatKey) o; + + if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null) + return false; + if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result; + result = (pattern != null ? pattern.hashCode() : 0); + result = 29 * result + (locale != null ? locale.hashCode() : 0); + return result; + } + } + + static class GetDefaultMessageReturnArg { + String message; + boolean foundInBundle; + + public GetDefaultMessageReturnArg(String message, boolean foundInBundle) { + this.message = message; + this.foundInBundle = foundInBundle; + } + } + + private static class EmptyResourceBundle extends ResourceBundle { + @Override + public Enumeration getKeys() { + return null; // dummy + } + + @Override + protected Object handleGetObject(String key) { + return null; // dummy + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/MemberAccessValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/MemberAccessValueStack.java new file mode 100644 index 000000000..369676fa6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/MemberAccessValueStack.java @@ -0,0 +1,14 @@ +package com.opensymphony.xwork2.util; + +import java.util.Set; +import java.util.regex.Pattern; + +/** + * ValueStacks implementing this interface provide a way to remove block or allow access + * to properties using regular expressions + */ +public interface MemberAccessValueStack { + void setExcludeProperties(Set excludeProperties); + + void setAcceptProperties(Set acceptedProperties); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/NamedVariablePatternMatcher.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/NamedVariablePatternMatcher.java new file mode 100644 index 000000000..a0bc6521b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/NamedVariablePatternMatcher.java @@ -0,0 +1,143 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An implementation of a pattern matcher that uses simple named wildcards. The named wildcards are defined using the + * {VARIABLE_NAME} syntax and will match any characters that aren't '/'. Internally, the pattern is + * converted into a regular expression where the named wildcard will be translated into ([^/]+) so that + * at least one character must match in order for the wildcard to be matched successfully. Matched values will be + * available in the variable map, indexed by the name they were given in the pattern. + * + *

For example, the following patterns will be processed as so: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PatternExampleVariable Map Contents
/animals/{animal} + * /animals/dog{animal -> dog}
/animals/{animal}/tag/No{id} + * /animals/dog/tag/No23{animal -> dog, id -> 23}
/{language} + * /en{language -> en}
+ * + *

+ * Excaping hasn't been implemented since the intended use of these patterns will be in matching URLs. + *

+ * + * @Since 2.1 + */ +public class NamedVariablePatternMatcher implements PatternMatcher { + + public boolean isLiteral(String pattern) { + return (pattern == null || pattern.indexOf('{') == -1); + } + + /** + * Compiles the pattern. + * + * @param data The pattern, must not be null or empty + * @return The compiled pattern, null if the pattern was null or empty + */ + public CompiledPattern compilePattern(String data) { + StringBuilder regex = new StringBuilder(); + if (data != null && data.length() > 0) { + List varNames = new ArrayList(); + StringBuilder varName = null; + for (int x=0; x map, String data, CompiledPattern expr) { + + if (data != null && data.length() > 0) { + Matcher matcher = expr.getPattern().matcher(data); + if (matcher.matches()) { + for (int x=0; x variableNames; + + + public CompiledPattern(Pattern pattern, List variableNames) { + this.pattern = pattern; + this.variableNames = variableNames; + } + + public Pattern getPattern() { + return pattern; + } + + public List getVariableNames() { + return variableNames; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/PatternMatcher.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/PatternMatcher.java new file mode 100644 index 000000000..f4728930d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/PatternMatcher.java @@ -0,0 +1,57 @@ +/* + * $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.util; + +import java.util.Map; + +/** + * Compiles and matches a pattern against a value + * + * @since 2.1 + */ +public interface PatternMatcher { + + /** + * Determines if the pattern is a simple literal string or contains wildcards that will need to be processed + * @param pattern The string pattern + * @return True if the pattern doesn't contain processing elements, false otherwise + */ + boolean isLiteral(String pattern); + + /** + *

Translate the given String into an object + * representing the pattern matchable by this class. + * + * @param data The string to translate. + * @return The encoded string + * @throws NullPointerException If data is null. + */ + E compilePattern(String data); + + /** + * Match a pattern against a string + * + * @param map The map to store matched values + * @param data The string to match + * @param expr The compiled wildcard expression + * @return True if a match + * @throws NullPointerException If any parameters are null + */ + boolean match(Map map, String data, E expr); + +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/PropertiesReader.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/PropertiesReader.java new file mode 100644 index 000000000..5a086c9ad --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/PropertiesReader.java @@ -0,0 +1,599 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.util; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to read properties lines. These lines do + * not terminate with new-line chars but rather when there is no + * backslash sign a the end of the line. This is used to + * concatenate multiple lines for readability. + * + * This class was pulled out of Jakarta Commons Configuration and + * Jakarta Commons Lang trunk revision 476093 + */ +public class PropertiesReader extends LineNumberReader +{ + /** Stores the comment lines for the currently processed property.*/ + private List commentLines; + + /** Stores the name of the last read property.*/ + private String propertyName; + + /** Stores the value of the last read property.*/ + private String propertyValue; + + /** Stores the list delimiter character.*/ + private char delimiter; + + /** Constant for the supported comment characters.*/ + static final String COMMENT_CHARS = "#!"; + + /** Constant for the radix of hex numbers.*/ + private static final int HEX_RADIX = 16; + + /** Constant for the length of a unicode literal.*/ + private static final int UNICODE_LEN = 4; + + /** The list of possible key/value separators */ + private static final char[] SEPARATORS = new char[] {'=', ':'}; + + /** The white space characters used as key/value separators. */ + private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'}; + + /** + * Constructor. + * + * @param reader A Reader. + */ + public PropertiesReader(Reader reader) + { + this(reader, ','); + } + + /** + * Creates a new instance of PropertiesReader and sets + * the underlaying reader and the list delimiter. + * + * @param reader the reader + * @param listDelimiter the list delimiter character + * @since 1.3 + */ + public PropertiesReader(Reader reader, char listDelimiter) + { + super(reader); + commentLines = new ArrayList(); + delimiter = listDelimiter; + } + + /** + * Tests whether a line is a comment, i.e. whether it starts with a comment + * character. + * + * @param line the line + * @return a flag if this is a comment line + * @since 1.3 + */ + boolean isCommentLine(String line) + { + String s = line.trim(); + // blanc lines are also treated as comment lines + return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0; + } + + /** + * Reads a property line. Returns null if Stream is + * at EOF. Concatenates lines ending with "\". + * Skips lines beginning with "#" or "!" and empty lines. + * The return value is a property definition (<name> + * = <value>) + * + * @return A string containing a property value or null + * + * @throws IOException in case of an I/O error + */ + public String readProperty() throws IOException + { + commentLines.clear(); + StringBuilder buffer = new StringBuilder(); + + while (true) + { + String line = readLine(); + if (line == null) + { + // EOF + return null; + } + + if (isCommentLine(line)) + { + commentLines.add(line); + continue; + } + + line = line.trim(); + + if (checkCombineLines(line)) + { + line = line.substring(0, line.length() - 1); + buffer.append(line); + } + else + { + buffer.append(line); + break; + } + } + return buffer.toString(); + } + + /** + * Parses the next property from the input stream and stores the found + * name and value in internal fields. These fields can be obtained using + * the provided getter methods. The return value indicates whether EOF + * was reached (false) or whether further properties are + * available (true). + * + * @return a flag if further properties are available + * @throws IOException if an error occurs + * @since 1.3 + */ + public boolean nextProperty() throws IOException + { + String line = readProperty(); + + if (line == null) + { + return false; // EOF + } + + // parse the line + String[] property = parseProperty(line); + propertyName = unescapeJava(property[0]); + propertyValue = unescapeJava(property[1], delimiter); + return true; + } + + /** + * Returns the comment lines that have been read for the last property. + * + * @return the comment lines for the last property returned by + * readProperty() + * @since 1.3 + */ + public List getCommentLines() + { + return commentLines; + } + + /** + * Returns the name of the last read property. This method can be called + * after {@link #nextProperty()} was invoked and its + * return value was true. + * + * @return the name of the last read property + * @since 1.3 + */ + public String getPropertyName() + { + return propertyName; + } + + /** + * Returns the value of the last read property. This method can be + * called after {@link #nextProperty()} was invoked and + * its return value was true. + * + * @return the value of the last read property + * @since 1.3 + */ + public String getPropertyValue() + { + return propertyValue; + } + + /** + * Checks if the passed in line should be combined with the following. + * This is true, if the line ends with an odd number of backslashes. + * + * @param line the line + * @return a flag if the lines should be combined + */ + private boolean checkCombineLines(String line) + { + int bsCount = 0; + for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) + { + bsCount++; + } + + return bsCount % 2 == 1; + } + + /** + * Parse a property line and return the key and the value in an array. + * + * @param line the line to parse + * @return an array with the property's key and value + * @since 1.2 + */ + private String[] parseProperty(String line) + { + // sorry for this spaghetti code, please replace it as soon as + // possible with a regexp when the Java 1.3 requirement is dropped + + String[] result = new String[2]; + StringBuilder key = new StringBuilder(); + StringBuilder value = new StringBuilder(); + + // state of the automaton: + // 0: key parsing + // 1: antislash found while parsing the key + // 2: separator crossing + // 3: value parsing + int state = 0; + + for (int pos = 0; pos < line.length(); pos++) + { + char c = line.charAt(pos); + + switch (state) + { + case 0: + if (c == '\\') + { + state = 1; + } + else if (contains(WHITE_SPACE, c)) + { + // switch to the separator crossing state + state = 2; + } + else if (contains(SEPARATORS, c)) + { + // switch to the value parsing state + state = 3; + } + else + { + key.append(c); + } + + break; + + case 1: + if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) + { + // this is an escaped separator or white space + key.append(c); + } + else + { + // another escaped character, the '\' is preserved + key.append('\\'); + key.append(c); + } + + // return to the key parsing state + state = 0; + + break; + + case 2: + if (contains(WHITE_SPACE, c)) + { + // do nothing, eat all white spaces + state = 2; + } + else if (contains(SEPARATORS, c)) + { + // switch to the value parsing state + state = 3; + } + else + { + // any other character indicates we encoutered the beginning of the value + value.append(c); + + // switch to the value parsing state + state = 3; + } + + break; + + case 3: + value.append(c); + break; + } + } + + result[0] = key.toString().trim(); + result[1] = value.toString().trim(); + + return result; + } + + /** + *

Unescapes any Java literals found in the String to a + * Writer.

This is a slightly modified version of the + * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't + * drop escaped separators (i.e '\,'). + * + * @param str the String to unescape, may be null + * @param delimiter the delimiter for multi-valued properties + * @return the processed string + * @throws IllegalArgumentException if the Writer is null + */ + protected static String unescapeJava(String str, char delimiter) + { + if (str == null) + { + return null; + } + int sz = str.length(); + StringBuilder out = new StringBuilder(sz); + StringBuffer unicode = new StringBuffer(UNICODE_LEN); + boolean hadSlash = false; + boolean inUnicode = false; + for (int i = 0; i < sz; i++) + { + char ch = str.charAt(i); + if (inUnicode) + { + // if in unicode, then we're reading unicode + // values in somehow + unicode.append(ch); + if (unicode.length() == UNICODE_LEN) + { + // unicode now contains the four hex digits + // which represents our unicode character + try + { + int value = Integer.parseInt(unicode.toString(), HEX_RADIX); + out.append((char) value); + unicode.setLength(0); + inUnicode = false; + hadSlash = false; + } + catch (NumberFormatException nfe) + { + throw new RuntimeException("Unable to parse unicode value: " + unicode, nfe); + } + } + continue; + } + + if (hadSlash) + { + // handle an escaped value + hadSlash = false; + + if (ch == '\\') + { + out.append('\\'); + } + else if (ch == '\'') + { + out.append('\''); + } + else if (ch == '\"') + { + out.append('"'); + } + else if (ch == 'r') + { + out.append('\r'); + } + else if (ch == 'f') + { + out.append('\f'); + } + else if (ch == 't') + { + out.append('\t'); + } + else if (ch == 'n') + { + out.append('\n'); + } + else if (ch == 'b') + { + out.append('\b'); + } + else if (ch == delimiter) + { + out.append('\\'); + out.append(delimiter); + } + else if (ch == 'u') + { + // uh-oh, we're in unicode country.... + inUnicode = true; + } + else + { + out.append(ch); + } + + continue; + } + else if (ch == '\\') + { + hadSlash = true; + continue; + } + out.append(ch); + } + + if (hadSlash) + { + // then we're in the weird case of a \ at the end of the + // string, let's output it anyway. + out.append('\\'); + } + + return out.toString(); + } + + /** + *

Checks if the object is in the given array.

+ * + *

The method returns false if a null array is passed in.

+ * + * @param array the array to search through + * @param objectToFind the object to find + * @return true if the array contains the object + */ + public boolean contains(char[] array, char objectToFind) { + if (array == null) { + return false; + } + for (char anArray : array) { + if (objectToFind == anArray) { + return true; + } + } + return false; + } + + /** + *

Unescapes any Java literals found in the String. + * For example, it will turn a sequence of '\' and + * 'n' into a newline character, unless the '\' + * is preceded by another '\'.

+ * + * @param str the String to unescape, may be null + * @return a new unescaped String, null if null string input + */ + public static String unescapeJava(String str) { + if (str == null) { + return null; + } + try { + StringWriter writer = new StringWriter(str.length()); + unescapeJava(writer, str); + return writer.toString(); + } catch (IOException ioe) { + // this should never ever happen while writing to a StringWriter + ioe.printStackTrace(); + return null; + } + } + + /** + *

Unescapes any Java literals found in the String to a + * Writer.

+ * + *

For example, it will turn a sequence of '\' and + * 'n' into a newline character, unless the '\' + * is preceded by another '\'.

+ * + *

A null string input has no effect.

+ * + * @param out the Writer used to output unescaped characters + * @param str the String to unescape, may be null + * @throws IllegalArgumentException if the Writer is null + * @throws IOException if error occurs on underlying Writer + */ + public static void unescapeJava(Writer out, String str) throws IOException { + if (out == null) { + throw new IllegalArgumentException("The Writer must not be null"); + } + if (str == null) { + return; + } + int sz = str.length(); + StringBuffer unicode = new StringBuffer(4); + boolean hadSlash = false; + boolean inUnicode = false; + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + if (inUnicode) { + // if in unicode, then we're reading unicode + // values in somehow + unicode.append(ch); + if (unicode.length() == 4) { + // unicode now contains the four hex digits + // which represents our unicode character + try { + int value = Integer.parseInt(unicode.toString(), 16); + out.write((char) value); + unicode.setLength(0); + inUnicode = false; + hadSlash = false; + } catch (NumberFormatException nfe) { + throw new RuntimeException("Unable to parse unicode value: " + unicode, nfe); + } + } + continue; + } + if (hadSlash) { + // handle an escaped value + hadSlash = false; + switch (ch) { + case '\\': + out.write('\\'); + break; + case '\'': + out.write('\''); + break; + case '\"': + out.write('"'); + break; + case 'r': + out.write('\r'); + break; + case 'f': + out.write('\f'); + break; + case 't': + out.write('\t'); + break; + case 'n': + out.write('\n'); + break; + case 'b': + out.write('\b'); + break; + case 'u': + { + // uh-oh, we're in unicode country.... + inUnicode = true; + break; + } + default : + out.write(ch); + break; + } + continue; + } else if (ch == '\\') { + hadSlash = true; + continue; + } + out.write(ch); + } + if (hadSlash) { + // then we're in the weird case of a \ at the end of the + // string, let's output it anyway. + out.write('\\'); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ResolverUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ResolverUtil.java new file mode 100644 index 000000000..0e583f92e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ResolverUtil.java @@ -0,0 +1,458 @@ +/* Copyright 2005-2006 Tim Fennell + * + * 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.util; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + *

ResolverUtil is used to locate classes that are available in the/a class path and meet + * arbitrary conditions. The two most common conditions are that a class implements/extends + * another class, or that is it annotated with a specific annotation. However, through the use + * of the {@link Test} class it is possible to search using arbitrary conditions.

+ * + *

A ClassLoader is used to locate all locations (directories and jar files) in the class + * path that contain classes within certain packages, and then to load those classes and + * check them. By default the ClassLoader returned by + * {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden + * by calling {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()} + * methods.

+ * + *

General searches are initiated by calling the + * {@link #find(com.opensymphony.xwork2.util.ResolverUtil.Test, String...)} ()} method and supplying + * a package name and a Test instance. This will cause the named package and all sub-packages + * to be scanned for classes that meet the test. There are also utility methods for the common + * use cases of scanning multiple packages for extensions of particular classes, or classes + * annotated with a specific annotation.

+ * + *

The standard usage pattern for the ResolverUtil class is as follows:

+ * + *
+ *ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
+ *resolver.findImplementation(ActionBean.class, pkg1, pkg2);
+ *resolver.find(new CustomTest(), pkg1);
+ *resolver.find(new CustomTest(), pkg2);
+ *Collection<ActionBean> beans = resolver.getClasses();
+ *
+ * + *

This class was copied from Stripes - http://stripes.mc4j.org/confluence/display/stripes/Home + *

+ * + * @author Tim Fennell + */ +public class ResolverUtil { + /** An instance of Log to use for logging in this class. */ + private static final Logger LOG = LoggerFactory.getLogger(ResolverUtil.class); + + /** + * A simple interface that specifies how to test classes to determine if they + * are to be included in the results produced by the ResolverUtil. + */ + public static interface Test { + /** + * Will be called repeatedly with candidate classes. Must return True if a class + * is to be included in the results, false otherwise. + */ + boolean matches(Class type); + + boolean matches(URL resource); + + boolean doesMatchClass(); + boolean doesMatchResource(); + } + + public static abstract class ClassTest implements Test { + public boolean matches(URL resource) { + throw new UnsupportedOperationException(); + } + + public boolean doesMatchClass() { + return true; + } + public boolean doesMatchResource() { + return false; + } + } + + public static abstract class ResourceTest implements Test { + public boolean matches(Class cls) { + throw new UnsupportedOperationException(); + } + + public boolean doesMatchClass() { + return false; + } + public boolean doesMatchResource() { + return true; + } + } + + /** + * A Test that checks to see if each class is assignable to the provided class. Note + * that this test will match the parent type itself if it is presented for matching. + */ + public static class IsA extends ClassTest { + private Class parent; + + /** Constructs an IsA test using the supplied Class as the parent class/interface. */ + public IsA(Class parentType) { this.parent = parentType; } + + /** Returns true if type is assignable to the parent type supplied in the constructor. */ + public boolean matches(Class type) { + return type != null && parent.isAssignableFrom(type); + } + + @Override public String toString() { + return "is assignable to " + parent.getSimpleName(); + } + } + + /** + * A Test that checks to see if each class name ends with the provided suffix. + */ + public static class NameEndsWith extends ClassTest { + private String suffix; + + /** Constructs a NameEndsWith test using the supplied suffix. */ + public NameEndsWith(String suffix) { this.suffix = suffix; } + + /** Returns true if type name ends with the suffix supplied in the constructor. */ + public boolean matches(Class type) { + return type != null && type.getName().endsWith(suffix); + } + + @Override public String toString() { + return "ends with the suffix " + suffix; + } + } + + /** + * A Test that checks to see if each class is annotated with a specific annotation. If it + * is, then the test returns true, otherwise false. + */ + public static class AnnotatedWith extends ClassTest { + private Class annotation; + + /** Construts an AnnotatedWith test for the specified annotation type. */ + public AnnotatedWith(Class annotation) { this.annotation = annotation; } + + /** Returns true if the type is annotated with the class provided to the constructor. */ + public boolean matches(Class type) { + return type != null && type.isAnnotationPresent(annotation); + } + + @Override public String toString() { + return "annotated with @" + annotation.getSimpleName(); + } + } + + public static class NameIs extends ResourceTest { + private String name; + + public NameIs(String name) { this.name = "/" + name; } + + public boolean matches(URL resource) { + return (resource.getPath().endsWith(name)); + } + + @Override public String toString() { + return "named " + name; + } + } + + /** The set of matches being accumulated. */ + private Set> classMatches = new HashSet>(); + + /** The set of matches being accumulated. */ + private Set resourceMatches = new HashSet(); + + /** + * The ClassLoader to use when looking for classes. If null then the ClassLoader returned + * by Thread.currentThread().getContextClassLoader() will be used. + */ + private ClassLoader classloader; + + /** + * Provides access to the classes discovered so far. If no calls have been made to + * any of the {@code find()} methods, this set will be empty. + * + * @return the set of classes that have been discovered. + */ + public Set> getClasses() { + return classMatches; + } + + public Set getResources() { + return resourceMatches; + } + + + /** + * Returns the classloader that will be used for scanning for classes. If no explicit + * ClassLoader has been set by the calling, the context class loader will be used. + * + * @return the ClassLoader that will be used to scan for classes + */ + public ClassLoader getClassLoader() { + return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader; + } + + /** + * Sets an explicit ClassLoader that should be used when scanning for classes. If none + * is set then the context classloader will be used. + * + * @param classloader a ClassLoader to use when scanning for classes + */ + public void setClassLoader(ClassLoader classloader) { this.classloader = classloader; } + + /** + * Attempts to discover classes that are assignable to the type provided. In the case + * that an interface is provided this method will collect implementations. In the case + * of a non-interface class, subclasses will be collected. Accumulated classes can be + * accessed by calling {@link #getClasses()}. + * + * @param parent the class of interface to find subclasses or implementations of + * @param packageNames one or more package names to scan (including subpackages) for classes + */ + public void findImplementations(Class parent, String... packageNames) { + if (packageNames == null) return; + + Test test = new IsA(parent); + for (String pkg : packageNames) { + findInPackage(test, pkg); + } + } + + /** + * Attempts to discover classes who's name ends with the provided suffix. Accumulated classes can be + * accessed by calling {@link #getClasses()}. + * + * @param suffix The class name suffix to match + * @param packageNames one or more package names to scan (including subpackages) for classes + */ + public void findSuffix(String suffix, String... packageNames) { + if (packageNames == null) return; + + Test test = new NameEndsWith(suffix); + for (String pkg : packageNames) { + findInPackage(test, pkg); + } + } + + /** + * Attempts to discover classes that are annotated with to the annotation. Accumulated + * classes can be accessed by calling {@link #getClasses()}. + * + * @param annotation the annotation that should be present on matching classes + * @param packageNames one or more package names to scan (including subpackages) for classes + */ + public void findAnnotated(Class annotation, String... packageNames) { + if (packageNames == null) return; + + Test test = new AnnotatedWith(annotation); + for (String pkg : packageNames) { + findInPackage(test, pkg); + } + } + + public void findNamedResource(String name, String... pathNames) { + if (pathNames == null) return; + + Test test = new NameIs(name); + for (String pkg : pathNames) { + findInPackage(test, pkg); + } + } + + /** + * Attempts to discover classes that pass the test. Accumulated + * classes can be accessed by calling {@link #getClasses()}. + * + * @param test the test to determine matching classes + * @param packageNames one or more package names to scan (including subpackages) for classes + */ + public void find(Test test, String... packageNames) { + if (packageNames == null) return; + + for (String pkg : packageNames) { + findInPackage(test, pkg); + } + } + + /** + * Scans for classes starting at the package provided and descending into subpackages. + * Each class is offered up to the Test as it is discovered, and if the Test returns + * true the class is retained. Accumulated classes can be fetched by calling + * {@link #getClasses()}. + * + * @param test an instance of {@link Test} that will be used to filter classes + * @param packageName the name of the package from which to start scanning for + * classes, e.g. {@code net.sourceforge.stripes} + */ + public void findInPackage(Test test, String packageName) { + packageName = packageName.replace('.', '/'); + ClassLoader loader = getClassLoader(); + Enumeration urls; + + try { + urls = loader.getResources(packageName); + } + catch (IOException ioe) { + LOG.warn("Could not read package: " + packageName, ioe); + return; + } + + while (urls.hasMoreElements()) { + try { + String urlPath = urls.nextElement().getFile(); + urlPath = URLDecoder.decode(urlPath, "UTF-8"); + + // If it's a file in a directory, trim the stupid file: spec + if ( urlPath.startsWith("file:") ) { + urlPath = urlPath.substring(5); + } + + // Else it's in a JAR, grab the path to the jar + if (urlPath.indexOf('!') > 0) { + urlPath = urlPath.substring(0, urlPath.indexOf('!')); + } + + LOG.info("Scanning for classes in [" + urlPath + "] matching criteria: " + test); + File file = new File(urlPath); + if ( file.isDirectory() ) { + loadImplementationsInDirectory(test, packageName, file); + } + else { + loadImplementationsInJar(test, packageName, file); + } + } + catch (IOException ioe) { + LOG.warn("could not read entries", ioe); + } + } + } + + + /** + * Finds matches in a physical directory on a filesystem. Examines all + * files within a directory - if the File object is not a directory, and ends with .class + * the file is loaded and tested to see if it is acceptable according to the Test. Operates + * recursively to find classes within a folder structure matching the package structure. + * + * @param test a Test used to filter the classes that are discovered + * @param parent the package name up to this directory in the package hierarchy. E.g. if + * /classes is in the classpath and we wish to examine files in /classes/org/apache then + * the values of parent would be org/apache + * @param location a File object representing a directory + */ + private void loadImplementationsInDirectory(Test test, String parent, File location) { + File[] files = location.listFiles(); + StringBuilder builder = null; + + for (File file : files) { + builder = new StringBuilder(100); + builder.append(parent).append("/").append(file.getName()); + String packageOrClass = ( parent == null ? file.getName() : builder.toString() ); + + if (file.isDirectory()) { + loadImplementationsInDirectory(test, packageOrClass, file); + } + else if (isTestApplicable(test, file.getName())) { + addIfMatching(test, packageOrClass); + } + } + } + + private boolean isTestApplicable(Test test, String path) { + return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass(); + } + + /** + * Finds matching classes within a jar files that contains a folder structure + * matching the package structure. If the File is not a JarFile or does not exist a warning + * will be logged, but no error will be raised. + * + * @param test a Test used to filter the classes that are discovered + * @param parent the parent package under which classes must be in order to be considered + * @param jarfile the jar file to be examined for classes + */ + private void loadImplementationsInJar(Test test, String parent, File jarfile) { + + try { + JarEntry entry; + JarInputStream jarStream = new JarInputStream(new FileInputStream(jarfile)); + + while ( (entry = jarStream.getNextJarEntry() ) != null) { + String name = entry.getName(); + if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) { + addIfMatching(test, name); + } + } + } + catch (IOException ioe) { + LOG.error("Could not search jar file '" + jarfile + "' for classes matching criteria: " + + test + " due to an IOException", ioe); + } + } + + /** + * Add the class designated by the fully qualified class name provided to the set of + * resolved classes if and only if it is approved by the Test supplied. + * + * @param test the test used to determine if the class matches + * @param fqn the fully qualified name of a class + */ + protected void addIfMatching(Test test, String fqn) { + try { + ClassLoader loader = getClassLoader(); + if (test.doesMatchClass()) { + String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.'); + if (LOG.isDebugEnabled()) { + LOG.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); + } + + Class type = loader.loadClass(externalName); + if (test.matches(type) ) { + classMatches.add( (Class) type); + } + } + if (test.doesMatchResource()) { + URL url = loader.getResource(fqn); + if (url == null) { + url = loader.getResource(fqn.substring(1)); + } + if (url != null && test.matches(url)) { + resourceMatches.add(url); + } + } + } + catch (Throwable t) { + LOG.warn("Could not examine class '" + fqn + "' due to a " + + t.getClass().getName() + " with message: " + t.getMessage()); + } + } +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java new file mode 100644 index 000000000..e00933498 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java @@ -0,0 +1,284 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; + + +/** + * Utility class for text parsing. + * + * @author Jason Carreira + * @author Rainer Hermanns + * @author tm_jee + * + * @version $Date$ $Id$ + */ +public class TextParseUtil { + + private static final int MAX_RECURSION = 1; + + /** + * Converts all instances of ${...}, and %{...} in expression to the value returned + * by a call to {@link ValueStack#findValue(java.lang.String)}. If an item cannot + * be found on the stack (null is returned), then the entire variable ${...} is not + * displayed, just as if the item was on the stack but returned an empty string. + * + * @param expression an expression that hasn't yet been translated + * @return the parsed expression + */ + public static String translateVariables(String expression, ValueStack stack) { + return translateVariables(new char[]{'$', '%'}, expression, stack, String.class, null).toString(); + } + + + /** + * Function similarly as {@link #translateVariables(char, String, ValueStack)} + * except for the introduction of an additional evaluator that allows + * the parsed value to be evaluated by the evaluator. The evaluator + * could be null, if it is it will just be skipped as if it is just calling + * {@link #translateVariables(char, String, ValueStack)}. + * + *

+ * + * A typical use-case would be when we need to URL Encode the parsed value. To do so + * we could just supply a URLEncodingEvaluator for example. + * + * @param expression + * @param stack + * @param evaluator The parsed Value evaluator (could be null). + * @return the parsed (and possibly evaluated) variable String. + */ + public static String translateVariables(String expression, ValueStack stack, ParsedValueEvaluator evaluator) { + return translateVariables(new char[]{'$', '%'}, expression, stack, String.class, evaluator).toString(); + } + + /** + * Converts all instances of ${...} in expression to the value returned + * by a call to {@link ValueStack#findValue(java.lang.String)}. If an item cannot + * be found on the stack (null is returned), then the entire variable ${...} is not + * displayed, just as if the item was on the stack but returned an empty string. + * + * @param open + * @param expression + * @param stack + * @return Translated variable String + */ + public static String translateVariables(char open, String expression, ValueStack stack) { + return translateVariables(open, expression, stack, String.class, null).toString(); + } + + /** + * Converted object from variable translation. + * + * @param open + * @param expression + * @param stack + * @param asType + * @return Converted object from variable translation. + */ + public static Object translateVariables(char open, String expression, ValueStack stack, Class asType) { + return translateVariables(open, expression, stack, asType, null); + } + + /** + * Converted object from variable translation. + * + * @param open + * @param expression + * @param stack + * @param asType + * @param evaluator + * @return Converted object from variable translation. + */ + public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) { + return translateVariables(new char[]{open} , expression, stack, asType, evaluator, MAX_RECURSION); + } + + /** + * Converted object from variable translation. + * + * @param open + * @param expression + * @param stack + * @param asType + * @param evaluator + * @return Converted object from variable translation. + */ + public static Object translateVariables(char[] openChars, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) { + return translateVariables(openChars, expression, stack, asType, evaluator, MAX_RECURSION); + } + + /** + * Converted object from variable translation. + * + * @param open + * @param expression + * @param stack + * @param asType + * @param evaluator + * @return Converted object from variable translation. + */ + public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator, int maxLoopCount) { + return translateVariables(new char[]{open}, expression, stack, asType, evaluator, maxLoopCount); + } + + /** + * Converted object from variable translation. + * + * @param open + * @param expression + * @param stack + * @param asType + * @param evaluator + * @return Converted object from variable translation. + */ + public static Object translateVariables(char[] openChars, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator, int maxLoopCount) { + // deal with the "pure" expressions first! + //expression = expression.trim(); + Object result = expression; + for (char open : openChars) { + int loopCount = 1; + int pos = 0; + + //this creates an implicit StringBuffer and shouldn't be used in the inner loop + final String lookupChars = open + "{"; + + while (true) { + int start = expression.indexOf(lookupChars, pos); + if (start == -1) { + pos = 0; + loopCount++; + start = expression.indexOf(lookupChars); + } + if (loopCount > maxLoopCount) { + // translateVariables prevent infinite loop / expression recursive evaluation + break; + } + int length = expression.length(); + int x = start + 2; + int end; + char c; + int count = 1; + while (start != -1 && x < length && count != 0) { + c = expression.charAt(x++); + if (c == '{') { + count++; + } else if (c == '}') { + count--; + } + } + end = x - 1; + + if ((start != -1) && (end != -1) && (count == 0)) { + String var = expression.substring(start + 2, end); + + Object o = stack.findValue(var, asType); + if (evaluator != null) { + o = evaluator.evaluate(o); + } + + + String left = expression.substring(0, start); + String right = expression.substring(end + 1); + String middle = null; + if (o != null) { + middle = o.toString(); + if (StringUtils.isEmpty(left)) { + result = o; + } else { + result = left.concat(middle); + } + + if (StringUtils.isNotEmpty(right)) { + result = result.toString().concat(right); + } + + expression = left.concat(middle).concat(right); + } else { + // the variable doesn't exist, so don't display anything + expression = left.concat(right); + result = expression; + } + pos = (left != null && left.length() > 0 ? left.length() - 1: 0) + + (middle != null && middle.length() > 0 ? middle.length() - 1: 0) + + 1; + pos = Math.max(pos, 1); + } else { + break; + } + } + } + + XWorkConverter conv = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class); + return conv.convertValue(stack.getContext(), result, asType); + } + + /** + * Returns a set from comma delimted Strings. + * @param s The String to parse. + * @return A set from comma delimted Strings. + */ + public static Set commaDelimitedStringToSet(String s) { + Set set = new HashSet(); + String[] split = s.split(","); + for (String aSplit : split) { + String trimmed = aSplit.trim(); + if (trimmed.length() > 0) + set.add(trimmed); + } + return set; + } + + + /** + * A parsed value evaluator for {@link TextParseUtil}. It could be supplied by + * calling {@link TextParseUtil#translateVariables(char, String, ValueStack, Class, ParsedValueEvaluator)}. + * + *

+ * + * By supplying this ParsedValueEvaluator, the parsed value + * (parsed against the value stack) value will be + * given to ParsedValueEvaluator to be evaluated before the + * translateVariable process goes on. + * + *

+ * + * A typical use-case would be to have a custom ParseValueEvaluator + * to URL Encode the parsed value. + * + * @author tm_jee + * + * @version $Date$ $Id$ + */ + public static interface ParsedValueEvaluator { + + /** + * Evaluated the value parsed by Ognl value stack. + * + * @param parsedValue - value parsed by ognl value stack + * @return return the evaluted value. + */ + Object evaluate(Object parsedValue); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/URLUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/URLUtil.java new file mode 100644 index 000000000..ebc1467a8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/URLUtil.java @@ -0,0 +1,105 @@ +/* + * 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.util; + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.net.URL; +import java.net.MalformedURLException; + +/** + * Helper class to extract file paths from different urls + */ +public class URLUtil { + + /** + * Prefix for Jar files in JBoss Virtual File System + */ + public static final String JBOSS5_VFSZIP = "vfszip"; + public static final String JBOSS5_VFSMEMORY = "vfsmemory"; + + private static final Pattern JAR_PATTERN = Pattern.compile("^(jar:|wsjar:|zip:|vfsfile:|code-source:)?(file:)?(.*?)(\\!/|.jar/)(.*)"); + private static final int JAR_FILE_PATH = 3; + + /** + * Convert URLs to URLs with "file" protocol + * @param url URL to convert to a jar url + * @return a URL to a file, or null if the URL external form cannot be parsed + */ + public static URL normalizeToFileProtocol(URL url) { + String fileName = url.toExternalForm(); + Matcher jarMatcher = JAR_PATTERN.matcher(fileName); + try { + if (isJBoss5Url(url)){ + return new URL("file", null, fileName.substring(fileName.indexOf(":") + 1)); + } else if (jarMatcher.matches()) { + String path = jarMatcher.group(JAR_FILE_PATH); + return new URL("file", "", path); + } else { + //it is not a jar or zip file + return null; + } + } catch (MalformedURLException e) { + //can this ever happen? + return null; + } + } + + /** + * Verify That the given String is in valid URL format. + * @param url The url string to verify. + * @return a boolean indicating whether the URL seems to be incorrect. + */ + public final static boolean verifyUrl(String url) { + if (url == null) { + return false; + } + + if (url.startsWith("https://")) { + // URL doesn't understand the https protocol, hack it + url = "http://" + url.substring(8); + } + + try { + new URL(url); + + return true; + } catch (MalformedURLException e) { + return false; + } + } + + /** + * Check if given URL is matching Jar pattern for different servers + * @param fileUrl + * @return + */ + public static boolean isJarURL(URL fileUrl) { + Matcher jarMatcher = URLUtil.JAR_PATTERN.matcher(fileUrl.getPath()); + return jarMatcher.matches(); + } + + /** + * Check if given URL is pointing to JBoss 5 VFS resource + * @param fileUrl + * @return + */ + public static boolean isJBoss5Url(URL fileUrl) { + final String protocol = fileUrl.getProtocol(); + return JBOSS5_VFSZIP.equals(protocol) || JBOSS5_VFSMEMORY.equals(fileUrl.getProtocol()); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java new file mode 100644 index 000000000..c2a208634 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java @@ -0,0 +1,150 @@ +/* + * 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.util; + +import java.util.Map; + +/** + * ValueStack allows multiple beans to be pushed in and dynamic EL expressions to be evaluated against it. When + * evaluating an expression, the stack will be searched down the stack, from the latest objects pushed in to the + * earliest, looking for a bean with a getter or setter for the given property or a method of the given name (depending + * on the expression being evaluated). + */ +public interface ValueStack { + + public static final String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack"; + + public static final String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork2.util.ValueStack.ReportErrorsOnNoProp"; + + /** + * Gets the context for this value stack. The context holds all the information in the value stack and it's surroundings. + * + * @return the context. + */ + public abstract Map getContext(); + + /** + * Sets the default type to convert to if no type is provided when getting a value. + * + * @param defaultType the new default type + */ + public abstract void setDefaultType(Class defaultType); + + /** + * Set a override map containing key -> values that takes precedent when doing find operations on the ValueStack. + *

+ * See the unit test for ValueStackTest for examples. + * + * @param overrides overrides map. + */ + public abstract void setExprOverrides(Map overrides); + + /** + * Gets the override map if anyone exists. + * + * @return the override map, null if not set. + */ + public abstract Map getExprOverrides(); + + /** + * Get the CompoundRoot which holds the objects pushed onto the stack + * + * @return the root + */ + public abstract CompoundRoot getRoot(); + + /** + * Attempts to set a property on a bean in the stack with the given expression using the default search order. + * + * @param expr the expression defining the path to the property to be set. + * @param value the value to be set into the named property + */ + public abstract void setValue(String expr, Object value); + + /** + * Attempts to set a property on a bean in the stack with the given expression using the default search order. + * + * @param expr the expression defining the path to the property to be set. + * @param value the value to be set into the named property + * @param throwExceptionOnFailure a flag to tell whether an exception should be thrown if there is no property with + * the given name. + */ + public abstract void setValue(String expr, Object value, boolean throwExceptionOnFailure); + + public abstract String findString(String expr); + public abstract String findString(String expr, boolean throwExceptionOnFailure); + + /** + * Find a value by evaluating the given expression against the stack in the default search order. + * + * @param expr the expression giving the path of properties to navigate to find the property value to return + * @return the result of evaluating the expression + */ + public abstract Object findValue(String expr); + + public abstract Object findValue(String expr, boolean throwExceptionOnFailure); + + /** + * Find a value by evaluating the given expression against the stack in the default search order. + * + * @param expr the expression giving the path of properties to navigate to find the property value to return + * @param asType the type to convert the return value to + * @return the result of evaluating the expression + */ + public abstract Object findValue(String expr, Class asType); + public abstract Object findValue(String expr, Class asType, boolean throwExceptionOnFailure); + + /** + * Get the object on the top of the stack without changing the stack. + * + * @return the object on the top. + * @see CompoundRoot#peek() + */ + public abstract Object peek(); + + /** + * Get the object on the top of the stack and remove it from the stack. + * + * @return the object on the top of the stack + * @see CompoundRoot#pop() + */ + public abstract Object pop(); + + /** + * Put this object onto the top of the stack + * + * @param o the object to be pushed onto the stack + * @see CompoundRoot#push(Object) + */ + public abstract void push(Object o); + + /** + * Sets an object on the stack with the given key + * so it is retrievable by {@link #findValue(String)}, {@link #findValue(String, Class)} + * + * @param key the key + * @param o the object + */ + public abstract void set(String key, Object o); + + /** + * Get the number of objects in the stack + * + * @return the number of objects in the stack + */ + public abstract int size(); + +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java new file mode 100644 index 000000000..aa8256d68 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java @@ -0,0 +1,38 @@ +/* + * 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.util; + +/** + * Factory that creates a value stack, defaulting to the OgnlValueStackFactory + */ +public interface ValueStackFactory { + + /** + * Get a new instance of {@link com.opensymphony.xwork2.util.ValueStack} + * + * @return a new {@link com.opensymphony.xwork2.util.ValueStack}. + */ + ValueStack createValueStack(); + + /** + * Get a new instance of {@link com.opensymphony.xwork2.util.ValueStack} + * + * @param stack an existing stack to include. + * @return a new {@link com.opensymphony.xwork2.util.ValueStack}. + */ + ValueStack createValueStack(ValueStack stack); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/WildcardHelper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/WildcardHelper.java new file mode 100644 index 000000000..86a3b9a80 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/WildcardHelper.java @@ -0,0 +1,463 @@ +/* + * $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.util; + +import java.util.Map; + +/** + * This class is an utility class that perform wilcard-patterns matching and + * isolation taken from Apache Cocoon. + * + * @version $Rev$ $Date: 2005-05-07 12:11:38 -0400 (Sat, 07 May 2005) + * $ + */ +public class WildcardHelper implements PatternMatcher { + /** + * The int representing '*' in the pattern int []. + */ + protected static final int MATCH_FILE = -1; + + /** + * The int representing '**' in the pattern int []. + */ + protected static final int MATCH_PATH = -2; + + /** + * The int representing begin in the pattern int []. + */ + protected static final int MATCH_BEGIN = -4; + + /** + * The int representing end in pattern int []. + */ + protected static final int MATCH_THEEND = -5; + + /** + * The int value that terminates the pattern int []. + */ + protected static final int MATCH_END = -3; + + /** + * Determines if the pattern contains any * characters + * + * @param pattern The pattern + * @return True if no wildcards are found + */ + public boolean isLiteral(String pattern) { + return (pattern == null || pattern.indexOf('*') == -1); + } + + /** + *

Translate the given String into a int [] + * representing the pattern matchable by this class.
This function + * translates a String into an int array converting the + * special '*' and '\' characters.
Here is how the conversion + * algorithm works:

+ * + *
    + * + *
  • The '*' character is converted to MATCH_FILE, meaning that zero or + * more characters (excluding the path separator '/') are to be + * matched.
  • + * + *
  • The '**' sequence is converted to MATCH_PATH, meaning that zero or + * more characters (including the path separator '/') are to be + * matched.
  • + * + *
  • The '\' character is used as an escape sequence ('\*' is translated + * in '*', not in MATCH_FILE). If an exact '\' character is to be matched + * the source string must contain a '\\'. sequence.
  • + * + *
+ * + *

When more than two '*' characters, not separated by another + * character, are found their value is considered as '**' (MATCH_PATH). + *
The array is always terminated by a special value (MATCH_END). + *
All MATCH* values are less than zero, while normal characters are + * equal or greater.

+ * + * @param data The string to translate. + * @return The encoded string as an int array, terminated by the MATCH_END + * value (don't consider the array length). + * @throws NullPointerException If data is null. + */ + public int[] compilePattern(String data) { + // Prepare the arrays + int[] expr = new int[data.length() + 2]; + char[] buff = data.toCharArray(); + + // Prepare variables for the translation loop + int y = 0; + boolean slash = false; + + // Must start from beginning + expr[y++] = MATCH_BEGIN; + + if (buff.length > 0) { + if (buff[0] == '\\') { + slash = true; + } else if (buff[0] == '*') { + expr[y++] = MATCH_FILE; + } else { + expr[y++] = buff[0]; + } + + // Main translation loop + for (int x = 1; x < buff.length; x++) { + // If the previous char was '\' simply copy this char. + if (slash) { + expr[y++] = buff[x]; + slash = false; + + // If the previous char was not '\' we have to do a bunch of + // checks + } else { + // If this char is '\' declare that and continue + if (buff[x] == '\\') { + slash = true; + + // If this char is '*' check the previous one + } else if (buff[x] == '*') { + // If the previous character als was '*' match a path + if (expr[y - 1] <= MATCH_FILE) { + expr[y - 1] = MATCH_PATH; + } else { + expr[y++] = MATCH_FILE; + } + } else { + expr[y++] = buff[x]; + } + } + } + } + + // Must match end at the end + expr[y] = MATCH_THEEND; + + return expr; + } + + /** + * Match a pattern agains a string and isolates wildcard replacement into + * a Stack. + * + * @param map The map to store matched values + * @param data The string to match + * @param expr The compiled wildcard expression + * @return True if a match + * @throws NullPointerException If any parameters are null + */ + public boolean match(Map map, String data, int[] expr) { + if (map == null) { + throw new NullPointerException("No map provided"); + } + + if (data == null) { + throw new NullPointerException("No data provided"); + } + + if (expr == null) { + throw new NullPointerException("No pattern expression provided"); + } + + char[] buff = data.toCharArray(); + + // Allocate the result buffer + char[] rslt = new char[expr.length + buff.length]; + + // The previous and current position of the expression character + // (MATCH_*) + int charpos = 0; + + // The position in the expression, input, translation and result arrays + int exprpos = 0; + int buffpos = 0; + int rsltpos = 0; + int offset = -1; + + // The matching count + int mcount = 0; + + // We want the complete data be in {0} + map.put(Integer.toString(mcount), data); + + // First check for MATCH_BEGIN + boolean matchBegin = false; + + if (expr[charpos] == MATCH_BEGIN) { + matchBegin = true; + exprpos = ++charpos; + } + + // Search the fist expression character (except MATCH_BEGIN - already + // skipped) + while (expr[charpos] >= 0) { + charpos++; + } + + // The expression charater (MATCH_*) + int exprchr = expr[charpos]; + + while (true) { + // Check if the data in the expression array before the current + // expression character matches the data in the input buffer + if (matchBegin) { + if (!matchArray(expr, exprpos, charpos, buff, buffpos)) { + return (false); + } + + matchBegin = false; + } else { + offset = indexOfArray(expr, exprpos, charpos, buff, buffpos); + + if (offset < 0) { + return (false); + } + } + + // Check for MATCH_BEGIN + if (matchBegin) { + if (offset != 0) { + return (false); + } + + matchBegin = false; + } + + // Advance buffpos + buffpos += (charpos - exprpos); + + // Check for END's + if (exprchr == MATCH_END) { + if (rsltpos > 0) { + map.put(Integer.toString(++mcount), + new String(rslt, 0, rsltpos)); + } + + // Don't care about rest of input buffer + return (true); + } else if (exprchr == MATCH_THEEND) { + if (rsltpos > 0) { + map.put(Integer.toString(++mcount), + new String(rslt, 0, rsltpos)); + } + + // Check that we reach buffer's end + return (buffpos == buff.length); + } + + // Search the next expression character + exprpos = ++charpos; + + while (expr[charpos] >= 0) { + charpos++; + } + + int prevchr = exprchr; + + exprchr = expr[charpos]; + + // We have here prevchr == * or **. + offset = + (prevchr == MATCH_FILE) + ? indexOfArray(expr, exprpos, charpos, buff, buffpos) + : lastIndexOfArray(expr, exprpos, charpos, buff, buffpos); + + if (offset < 0) { + return (false); + } + + // Copy the data from the source buffer into the result buffer + // to substitute the expression character + if (prevchr == MATCH_PATH) { + while (buffpos < offset) { + rslt[rsltpos++] = buff[buffpos++]; + } + } else { + // Matching file, don't copy '/' + while (buffpos < offset) { + if (buff[buffpos] == '/') { + return (false); + } + + rslt[rsltpos++] = buff[buffpos++]; + } + } + + map.put(Integer.toString(++mcount), new String(rslt, 0, rsltpos)); + rsltpos = 0; + } + } + + /** + * Get the offset of a part of an int array within a char array.
This + * method return the index in d of the first occurrence after dpos of that + * part of array specified by r, starting at rpos and terminating at + * rend. + * + * @param r The array containing the data that need to be matched in + * d. + * @param rpos The index of the first character in r to look for. + * @param rend The index of the last character in r to look for plus 1. + * @param d The array of char that should contain a part of r. + * @param dpos The starting offset in d for the matching. + * @return The offset in d of the part of r matched in d or -1 if that was + * not found. + */ + protected int indexOfArray(int[] r, int rpos, int rend, char[] d, int dpos) { + // Check if pos and len are legal + if (rend < rpos) { + throw new IllegalArgumentException("rend < rpos"); + } + + // If we need to match a zero length string return current dpos + if (rend == rpos) { + return (d.length); //?? dpos? + } + + // If we need to match a 1 char length string do it simply + if ((rend - rpos) == 1) { + // Search for the specified character + for (int x = dpos; x < d.length; x++) { + if (r[rpos] == d[x]) { + return (x); + } + } + } + + // Main string matching loop. It gets executed if the characters to + // match are less then the characters left in the d buffer + while (((dpos + rend) - rpos) <= d.length) { + // Set current startpoint in d + int y = dpos; + + // Check every character in d for equity. If the string is matched + // return dpos + for (int x = rpos; x <= rend; x++) { + if (x == rend) { + return (dpos); + } + + if (r[x] != d[y++]) { + break; + } + } + + // Increase dpos to search for the same string at next offset + dpos++; + } + + // The remaining chars in d buffer were not enough or the string + // wasn't matched + return (-1); + } + + /** + * Get the offset of a last occurance of an int array within a char array. + *
This method return the index in d of the last occurrence after + * dpos of that part of array specified by r, starting at rpos and + * terminating at rend. + * + * @param r The array containing the data that need to be matched in + * d. + * @param rpos The index of the first character in r to look for. + * @param rend The index of the last character in r to look for plus 1. + * @param d The array of char that should contain a part of r. + * @param dpos The starting offset in d for the matching. + * @return The offset in d of the last part of r matched in d or -1 if + * that was not found. + */ + protected int lastIndexOfArray(int[] r, int rpos, int rend, char[] d, + int dpos) { + // Check if pos and len are legal + if (rend < rpos) { + throw new IllegalArgumentException("rend < rpos"); + } + + // If we need to match a zero length string return current dpos + if (rend == rpos) { + return (d.length); //?? dpos? + } + + // If we need to match a 1 char length string do it simply + if ((rend - rpos) == 1) { + // Search for the specified character + for (int x = d.length - 1; x > dpos; x--) { + if (r[rpos] == d[x]) { + return (x); + } + } + } + + // Main string matching loop. It gets executed if the characters to + // match are less then the characters left in the d buffer + int l = d.length - (rend - rpos); + + while (l >= dpos) { + // Set current startpoint in d + int y = l; + + // Check every character in d for equity. If the string is matched + // return dpos + for (int x = rpos; x <= rend; x++) { + if (x == rend) { + return (l); + } + + if (r[x] != d[y++]) { + break; + } + } + + // Decrease l to search for the same string at next offset + l--; + } + + // The remaining chars in d buffer were not enough or the string + // wasn't matched + return (-1); + } + + /** + * Matches elements of array r from rpos to rend with array d, starting + * from dpos.
This method return true if elements of array r from + * rpos to rend equals elements of array d starting from dpos to + * dpos+(rend-rpos). + * + * @param r The array containing the data that need to be matched in + * d. + * @param rpos The index of the first character in r to look for. + * @param rend The index of the last character in r to look for. + * @param d The array of char that should start from a part of r. + * @param dpos The starting offset in d for the matching. + * @return true if array d starts from portion of array r. + */ + protected boolean matchArray(int[] r, int rpos, int rend, char[] d, int dpos) { + if ((d.length - dpos) < (rend - rpos)) { + return (false); + } + + for (int i = rpos; i < rend; i++) { + if (r[i] != d[dpos++]) { + return (false); + } + } + + return (true); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java new file mode 100644 index 000000000..52e0109d3 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java @@ -0,0 +1,226 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + + +/** + * A simple list that guarantees that inserting and retrieving objects will always work regardless + * of the current size of the list. Upon insertion, type conversion is also performed if necessary. + * Empty beans will be created to fill the gap between the current list size and the requested index + * using ObjectFactory's {@link ObjectFactory#buildBean(Class,java.util.Map) buildBean} method. + * + * @author Patrick Lightbody + */ +public class XWorkList extends ArrayList { + private static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class); + + private Class clazz; + private XWorkConverter conv; + + private ObjectFactory objectFactory; + + public XWorkList(ObjectFactory fac, XWorkConverter conv, Class clazz) { + this.conv = conv; + this.clazz = clazz; + this.objectFactory = fac; + } + + public XWorkList(ObjectFactory fac, XWorkConverter conv, Class clazz, int initialCapacity) { + super(initialCapacity); + this.clazz = clazz; + this.conv = conv; + this.objectFactory = fac; + } + + /** + * Inserts the specified element at the specified position in this list. Shifts the element + * currently at that position (if any) and any subsequent elements to the right (adds one to + * their indices). + *

+ * This method is guaranteed to work since it will create empty beans to fill the gap between + * the current list size and the requested index to enable the element to be set. This method + * also performs any necessary type conversion. + * + * @param index index at which the specified element is to be inserted. + * @param element element to be inserted. + */ + @Override + public void add(int index, Object element) { + if (index >= this.size()) { + get(index); + } + + element = convert(element); + + super.add(index, element); + } + + /** + * Appends the specified element to the end of this list. + *

+ * This method performs any necessary type conversion. + * + * @param element element to be appended to this list. + * @return true (as per the general contract of Collection.add). + */ + @Override + public boolean add(Object element) { + element = convert(element); + + return super.add(element); + } + + /** + * Appends all of the elements in the specified Collection to the end of this list, in the order + * that they are returned by the specified Collection's Iterator. The behavior of this + * operation is undefined if the specified Collection is modified while the operation is in + * progress. (This implies that the behavior of this call is undefined if the specified + * Collection is this list, and this list is nonempty.) + *

+ * This method performs any necessary type conversion. + * + * @param c the elements to be inserted into this list. + * @return true if this list changed as a result of the call. + * @throws NullPointerException if the specified collection is null. + */ + @Override + public boolean addAll(Collection c) { + if (c == null) { + throw new NullPointerException("Collection to add is null"); + } + + for (Object aC : c) { + add(aC); + } + + return true; + } + + /** + * Inserts all of the elements in the specified Collection into this list, starting at the + * specified position. Shifts the element currently at that position (if any) and any + * subsequent elements to the right (increases their indices). The new elements will appear in + * the list in the order that they are returned by the specified Collection's iterator. + *

+ * This method is guaranteed to work since it will create empty beans to fill the gap between + * the current list size and the requested index to enable the element to be set. This method + * also performs any necessary type conversion. + * + * @param index index at which to insert first element from the specified collection. + * @param c elements to be inserted into this list. + * @return true if this list changed as a result of the call. + */ + @Override + public boolean addAll(int index, Collection c) { + if (c == null) { + throw new NullPointerException("Collection to add is null"); + } + + boolean trim = false; + + if (index >= this.size()) { + trim = true; + } + + for (Iterator it = c.iterator(); it.hasNext(); index++) { + add(index, it.next()); + } + + if (trim) { + remove(this.size() - 1); + } + + return true; + } + + /** + * Returns the element at the specified position in this list. + *

+ * An object is guaranteed to be returned since it will create empty beans to fill the gap + * between the current list size and the requested index. + * + * @param index index of element to return. + * @return the element at the specified position in this list. + */ + @Override + public synchronized Object get(int index) { + while (index >= this.size()) { + try { + //todo + this.add(objectFactory.buildBean(clazz, null)); //ActionContext.getContext().getContextMap())); + } catch (Exception e) { + throw new XWorkException(e); + } + } + + return super.get(index); + } + + /** + * Replaces the element at the specified position in this list with the specified element. + *

+ * This method is guaranteed to work since it will create empty beans to fill the gap between + * the current list size and the requested index to enable the element to be set. This method + * also performs any necessary type conversion. + * + * @param index index of element to replace. + * @param element element to be stored at the specified position. + * @return the element previously at the specified position. + */ + @Override + public Object set(int index, Object element) { + if (index >= this.size()) { + get(index); + } + + element = convert(element); + + return super.set(index, element); + } + + private Object convert(Object element) { + if ((element != null) && !clazz.isAssignableFrom(element.getClass())) { + // convert to correct type + if (LOG.isDebugEnabled()) { + LOG.debug("Converting from " + element.getClass().getName() + " to " + clazz.getName()); + } + + Map context = ActionContext.getContext().getContextMap(); + element = conv.convertValue(context, null, null, null, element, clazz); + } + + return element; + } + + @Override + public boolean contains(Object element) { + element = convert(element); + + return super.contains(element); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java new file mode 100644 index 000000000..57083e0a3 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java @@ -0,0 +1,95 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.config.*; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +/** + * Generic test setup methods to be used with any unit testing framework. + */ +public class XWorkTestCaseHelper { + + public static ConfigurationManager setUp() throws Exception { + ConfigurationManager configurationManager = new ConfigurationManager(); + configurationManager.addContainerProvider(new XWorkConfigurationProvider()); + Configuration config = configurationManager.getConfiguration(); + Container container = config.getContainer(); + + // Reset the value stack + ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack(); + stack.getContext().put(ActionContext.CONTAINER, container); + ActionContext.setContext(new ActionContext(stack.getContext())); + + // clear out localization + LocalizedTextUtil.reset(); + + + //ObjectFactory.setObjectFactory(container.getInstance(ObjectFactory.class)); + return configurationManager; + } + + public static ConfigurationManager loadConfigurationProviders(ConfigurationManager configurationManager, + ConfigurationProvider... providers) { + try { + tearDown(configurationManager); + } catch (Exception e) { + throw new RuntimeException("Cannot clean old configuration", e); + } + configurationManager = new ConfigurationManager(); + configurationManager.addContainerProvider(new ContainerProvider() { + public void destroy() {} + public void init(Configuration configuration) throws ConfigurationException {} + public boolean needsReload() { return false; } + + public void register(ContainerBuilder builder, + LocatableProperties props) throws ConfigurationException { + builder.setAllowDuplicates(true); + } + + }); + configurationManager.addContainerProvider(new XWorkConfigurationProvider()); + for (ConfigurationProvider prov : providers) { + if (prov instanceof XmlConfigurationProvider) { + ((XmlConfigurationProvider)prov).setThrowExceptionOnDuplicateBeans(false); + } + configurationManager.addConfigurationProvider(prov); + } + Container container = configurationManager.getConfiguration().getContainer(); + + // Reset the value stack + ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack(); + stack.getContext().put(ActionContext.CONTAINER, container); + ActionContext.setContext(new ActionContext(stack.getContext())); + + return configurationManager; + } + + public static void tearDown(ConfigurationManager configurationManager) throws Exception { + + // clear out configuration + if (configurationManager != null) { + configurationManager.destroyConfiguration(); + configurationManager = null; + } + ActionContext.setContext(null); + } +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java new file mode 100644 index 000000000..4475449c5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.classloader; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + + +/** + * Reads a class from disk + * class taken from Apache JCI + */ +public final class FileResourceStore implements ResourceStore { + private static final Logger LOG = LoggerFactory.getLogger(FileResourceStore.class); + private final File root; + + public FileResourceStore(final File pFile) { + root = pFile; + } + + public byte[] read(final String pResourceName) { + FileInputStream fis = null; + try { + File file = getFile(pResourceName); + byte[] data = new byte[(int) file.length()]; + fis = new FileInputStream(file); + fis.read(data); + + return data; + } catch (Exception e) { + if (LOG.isDebugEnabled()) + LOG.debug("Unable to read file [#0]", e, pResourceName); + return null; + } finally { + closeQuietly(fis); + } + } + + public void write(final String pResourceName, final byte[] pData) { + + } + + private void closeQuietly(InputStream is) { + try { + if (is != null) + is.close(); + } catch (IOException e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to close file input stream", e); + } + } + + private File getFile(final String pResourceName) { + final String fileName = pResourceName.replace('/', File.separatorChar); + return new File(root, fileName); + } + + public String toString() { + return this.getClass().getName() + root.toString(); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java new file mode 100644 index 000000000..c991f759a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java @@ -0,0 +1,83 @@ +/* + * 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.util.classloader; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Read resources from a jar file + */ +public class JarResourceStore implements ResourceStore { + private static final Logger LOG = LoggerFactory.getLogger(JarResourceStore.class); + + private final File file; + + public JarResourceStore(File file) { + this.file = file; + } + + public void write(String pResourceName, byte[] pResourceData) { + } + + public byte[] read(String pResourceName) { + InputStream in = null; + try { + ZipFile jarFile = new ZipFile(file); + ZipEntry entry = jarFile.getEntry(pResourceName); + + //read into byte array + ByteArrayOutputStream out = new ByteArrayOutputStream(); + in = jarFile.getInputStream(entry); + copy(in, out); + + return out.toByteArray(); + } catch (Exception e) { + if (LOG.isDebugEnabled()) + LOG.debug("Unable to read file [#0] from [#1]", e, pResourceName, file.getName()); + return null; + } finally { + closeQuietly(in); + } + } + + public static long copy(InputStream input, OutputStream output) + throws IOException { + byte[] buffer = new byte[1024 * 4]; + long count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + private void closeQuietly(InputStream is) { + try { + if (is != null) + is.close(); + } catch (IOException e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to close input stream", e); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java new file mode 100644 index 000000000..0fec8efe1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java @@ -0,0 +1,177 @@ +/* + * 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.util.classloader; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.classloader.FileResourceStore; +import com.opensymphony.xwork2.util.URLUtil; +import com.opensymphony.xwork2.XWorkException; + +import java.io.InputStream; +import java.io.File; +import java.net.URL; +import java.net.URISyntaxException; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.Set; +import java.util.Collections; +import java.util.Collection; +import java.util.HashSet; + +import org.apache.commons.lang.ObjectUtils; + +/** + * The ReloadingClassLoader uses a delegation mechanism to allow + * classes to be reloaded. That means that loadClass calls may + * return different results if the class was changed in the underlying + * ResourceStore. + *

+ * class taken from Apache JCI + */ +public class ReloadingClassLoader extends ClassLoader { + private static final Logger LOG = LoggerFactory.getLogger(ReloadingClassLoader.class); + private final ClassLoader parent; + private ResourceStore[] stores; + private ClassLoader delegate; + + private Set acceptClasses = Collections.emptySet(); + + public ReloadingClassLoader(final ClassLoader pParent) { + super(pParent); + parent = pParent; + URL parentRoot = pParent.getResource(""); + URL root = URLUtil.normalizeToFileProtocol(parentRoot); + root = (URL) ObjectUtils.defaultIfNull(root, parentRoot); + try { + if (root != null) { + stores = new ResourceStore[]{new FileResourceStore(new File(root.toURI()))}; + } else { + throw new XWorkException("Unable to start the reloadable class loader, consider setting 'struts.convention.classes.reload' to false"); + } + } catch (URISyntaxException e) { + throw new XWorkException("Unable to start the reloadable class loader, consider setting 'struts.convention.classes.reload' to false", e); + } catch (RuntimeException e) { + // see WW-3121 + // TODO: Fix this for a reloading mechanism to be marked as stable + if (root != null) + LOG.error("Exception while trying to build the ResourceStore for URL [#0]", e, root.toString()); + else + LOG.error("Exception while trying to get root resource from class loader", e); + LOG.error("Consider setting struts.convention.classes.reload=false"); + throw e; + } + + delegate = new ResourceStoreClassLoader(parent, stores); + } + + public boolean addResourceStore(final ResourceStore pStore) { + try { + final int n = stores.length; + final ResourceStore[] newStores = new ResourceStore[n + 1]; + System.arraycopy(stores, 0, newStores, 1, n); + newStores[0] = pStore; + stores = newStores; + delegate = new ResourceStoreClassLoader(parent, stores); + return true; + } catch (final RuntimeException e) { + LOG.error("Could not add resource store", e); + } + return false; + } + + public boolean removeResourceStore(final ResourceStore pStore) { + + final int n = stores.length; + int i = 0; + + // FIXME: this should be improved with a Map + // find the pStore and index position with var i + while ((i < n) && (stores[i] != pStore)) { + i++; + } + + // pStore was not found + if (i == n) { + return false; + } + + // if stores length > 1 then array copy old values, else create new empty store + final ResourceStore[] newStores = new ResourceStore[n - 1]; + if (i > 0) { + System.arraycopy(stores, 0, newStores, 0, i); + } + if (i < n - 1) { + System.arraycopy(stores, i + 1, newStores, i, (n - i - 1)); + } + + stores = newStores; + delegate = new ResourceStoreClassLoader(parent, stores); + return true; + } + + public void reload() { + if (LOG.isTraceEnabled()) + LOG.trace("Reloading class loader"); + delegate = new ResourceStoreClassLoader(parent, stores); + } + + public void clearAssertionStatus() { + delegate.clearAssertionStatus(); + } + + public URL getResource(String name) { + return delegate.getResource(name); + } + + public InputStream getResourceAsStream(String name) { + return delegate.getResourceAsStream(name); + } + + public Class loadClass(String name) throws ClassNotFoundException { + return isAccepted(name) ? delegate.loadClass(name) : parent.loadClass(name); + } + + public void setClassAssertionStatus(String className, boolean enabled) { + delegate.setClassAssertionStatus(className, enabled); + } + + public void setDefaultAssertionStatus(boolean enabled) { + delegate.setDefaultAssertionStatus(enabled); + } + + public void setPackageAssertionStatus(String packageName, boolean enabled) { + delegate.setPackageAssertionStatus(packageName, enabled); + } + + public void setAccepClasses(Set acceptClasses) { + this.acceptClasses = acceptClasses; + } + + protected boolean isAccepted(String className) { + if (!this.acceptClasses.isEmpty()) { + for (Pattern pattern : acceptClasses) { + Matcher matcher = pattern.matcher(className); + if (matcher.matches()) { + return true; + } + } + return false; + } else + return true; + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java new file mode 100644 index 000000000..80e6ce958 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java @@ -0,0 +1,27 @@ +/* + * 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.util.classloader; + +/** + * *interface taken from Apache JCI + */ +public interface ResourceStore { + + void write(final String pResourceName, final byte[] pResourceData); + + byte[] read(final String pResourceName); +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java new file mode 100644 index 000000000..9cf38e1c0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java @@ -0,0 +1,83 @@ +/* + * 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.util.classloader; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + * class taken from Apache JCI + */ +public final class ResourceStoreClassLoader extends ClassLoader { + + private static final Logger LOG = LoggerFactory.getLogger(ResourceStoreClassLoader.class); + + private final ResourceStore[] stores; + + public ResourceStoreClassLoader(final ClassLoader pParent, final ResourceStore[] pStores) { + super(pParent); + + stores = new ResourceStore[pStores.length]; + System.arraycopy(pStores, 0, stores, 0, stores.length); + } + + private Class fastFindClass(final String name) { + + if (stores != null) { + String fileName = name.replace('.', '/') + ".class"; + for (final ResourceStore store : stores) { + final byte[] clazzBytes = store.read(fileName); + if (clazzBytes != null) { + return defineClass(name, clazzBytes, 0, clazzBytes.length); + } + } + } + + return null; + } + + protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + + if (clazz == null) { + clazz = fastFindClass(name); + + if (clazz == null) { + final ClassLoader parent = getParent(); + if (parent != null) { + clazz = parent.loadClass(name); + } else { + throw new ClassNotFoundException(name); + } + + } + } + + if (resolve) { + resolveClass(clazz); + } + + return clazz; + } + + protected Class findClass(final String name) throws ClassNotFoundException { + final Class clazz = fastFindClass(name); + if (clazz == null) { + throw new ClassNotFoundException(name); + } + return clazz; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java new file mode 100644 index 000000000..99f64e6d5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java @@ -0,0 +1,886 @@ +/* + * 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.util.finder; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.URLUtil; +import com.opensymphony.xwork2.XWorkException; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.EmptyVisitor; +import org.apache.commons.lang.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * ClassFinder searches the classpath of the specified ClassLoaderInterface for + * packages, classes, constructors, methods, or fields with specific annotations. + * + * For security reasons ASM is used to find the annotations. Classes are not + * loaded unless they match the requirements of a called findAnnotated* method. + * Once loaded, these classes are cached. + * + * The getClassesNotLoaded() method can be used immediately after any find* + * method to get a list of classes which matched the find requirements (i.e. + * contained the annotation), but were unable to be loaded. + * + * @author David Blevins + * @version $Rev$ $Date$ + */ +public class ClassFinder { + private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class); + + private final Map> annotated = new HashMap>(); + private final Map classInfos = new LinkedHashMap(); + + private final List classesNotLoaded = new ArrayList(); + + private boolean extractBaseInterfaces; + private ClassLoaderInterface classLoaderInterface; + + /** + * Creates a ClassFinder that will search the urls in the specified ClassLoaderInterface + * excluding the urls in the ClassLoaderInterface's parent. + * + * To include the parent ClassLoaderInterface, use: + * + * new ClassFinder(ClassLoaderInterface, false); + * + * To exclude the parent's parent, use: + * + * new ClassFinder(ClassLoaderInterface, ClassLoaderInterface.getParent().getParent()); + * + * @param classLoader source of classes to scan + * @throws Exception if something goes wrong + */ + public ClassFinder(ClassLoaderInterface classLoader) throws Exception { + this(classLoader, true); + } + + /** + * Creates a ClassFinder that will search the urls in the specified ClassLoaderInterface. + * + * @param classLoader source of classes to scan + * @param excludeParent Allegedly excludes classes from parent ClassLoaderInterface, whatever that might mean + * @throws Exception if something goes wrong. + */ + public ClassFinder(ClassLoaderInterface classLoader, boolean excludeParent) throws Exception { + this(classLoader, getUrls(classLoader, excludeParent)); + } + + /** + * Creates a ClassFinder that will search the urls in the specified classloader excluding + * the urls in the 'exclude' ClassLoaderInterface. + * + * @param classLoader source of classes to scan + * @param exclude source of classes to exclude from scanning + * @throws Exception if something goes wrong + */ + public ClassFinder(ClassLoaderInterface classLoader, ClassLoaderInterface exclude) throws Exception { + this(classLoader, getUrls(classLoader, exclude)); + } + + public ClassFinder(ClassLoaderInterface classLoader, URL url) { + this(classLoader, Arrays.asList(url)); + } + + public ClassFinder(ClassLoaderInterface classLoader, String... dirNames) { + this(classLoader, getURLs(classLoader, dirNames)); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls) { + this(classLoaderInterface, urls, false); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces) { + this(classLoaderInterface, urls, extractBaseInterfaces, new HashSet(){ + { + add("jar"); + } + }); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols) { + this(classLoaderInterface,urls,extractBaseInterfaces,protocols,new DefaultClassnameFilterImpl()); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { + this.classLoaderInterface = classLoaderInterface; + this.extractBaseInterfaces = extractBaseInterfaces; + + List classNames = new ArrayList(); + for (URL location : urls) { + try { + if (protocols.contains(location.getProtocol())) { + classNames.addAll(jar(location)); + } else if ("file".equals(location.getProtocol())) { + try { + // See if it's actually a jar + URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); + JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); + juc.getJarFile(); + classNames.addAll(jar(jarUrl)); + } catch (IOException e) { + classNames.addAll(file(location)); + } + } + } catch (Exception e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read URL [#0]", e, location.toExternalForm()); + } + } + + for (String className : classNames) { + try { + if (classNameFilter.test(className)) + readClassDef(className); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read class [#0]", e, className); + } + } + } + + public ClassFinder(Class... classes){ + this(Arrays.asList(classes)); + } + + public ClassFinder(List classes){ + this.classLoaderInterface = null; + List infos = new ArrayList(); + List packages = new ArrayList(); + for (Class clazz : classes) { + + Package aPackage = clazz.getPackage(); + if (aPackage != null && !packages.contains(aPackage)){ + infos.add(new PackageInfo(aPackage)); + packages.add(aPackage); + } + + ClassInfo classInfo = new ClassInfo(clazz); + infos.add(classInfo); + classInfos.put(classInfo.getName(), classInfo); + for (Method method : clazz.getDeclaredMethods()) { + infos.add(new MethodInfo(classInfo, method)); + } + + for (Constructor constructor : clazz.getConstructors()) { + infos.add(new MethodInfo(classInfo, constructor)); + } + + for (Field field : clazz.getDeclaredFields()) { + infos.add(new FieldInfo(classInfo, field)); + } + } + + for (Info info : infos) { + for (AnnotationInfo annotation : info.getAnnotations()) { + List annotationInfos = getAnnotationInfos(annotation.getName()); + annotationInfos.add(info); + } + } + } + + public boolean isAnnotationPresent(Class annotation) { + List infos = annotated.get(annotation.getName()); + return infos != null && !infos.isEmpty(); + } + + /** + * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. + *

+ * The list will only contain entries of classes whose byte code matched the requirements + * of last invoked find* method, but were unable to be loaded and included in the results. + *

+ * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the + * results from the last findAnnotated* method call. + *

+ * This method is not thread safe. + * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. + */ + public List getClassesNotLoaded() { + return Collections.unmodifiableList(classesNotLoaded); + } + + public List findAnnotatedPackages(Class annotation) { + classesNotLoaded.clear(); + List packages = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof PackageInfo) { + PackageInfo packageInfo = (PackageInfo) info; + try { + Package pkg = packageInfo.get(); + // double check via proper reflection + if (pkg.isAnnotationPresent(annotation)) { + packages.add(pkg); + } + } catch (ClassNotFoundException e) { + classesNotLoaded.add(packageInfo.getName()); + } + } + } + return packages; + } + + public List findAnnotatedClasses(Class annotation) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof ClassInfo) { + ClassInfo classInfo = (ClassInfo) info; + try { + Class clazz = classInfo.get(); + // double check via proper reflection + if (clazz.isAnnotationPresent(annotation)) { + classes.add(clazz); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return classes; + } + + public List findAnnotatedMethods(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List methods = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && !"".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(annotation)) { + methods.add(method); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return methods; + } + + public List findAnnotatedConstructors(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List constructors = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && "".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Constructor constructor : clazz.getConstructors()) { + if (constructor.isAnnotationPresent(annotation)) { + constructors.add(constructor); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return constructors; + } + + public List findAnnotatedFields(Class annotation) { + classesNotLoaded.clear(); + List seen = new ArrayList(); + List fields = new ArrayList(); + List infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof FieldInfo) { + FieldInfo fieldInfo = (FieldInfo) info; + ClassInfo classInfo = fieldInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(annotation)) { + fields.add(field); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return fields; + } + + public List findClassesInPackage(String packageName, boolean recursive) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (recursive && classInfo.getPackageName().startsWith(packageName)){ + classes.add(classInfo.get()); + } else if (classInfo.getPackageName().equals(packageName)){ + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List findClasses(Test test) { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (test.test(classInfo)) { + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List findClasses() { + classesNotLoaded.clear(); + List classes = new ArrayList(); + for (ClassInfo classInfo : classInfos.values()) { + try { + classes.add(classInfo.get()); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + private static List getURLs(ClassLoaderInterface classLoader, String[] dirNames) { + List urls = new ArrayList(); + for (String dirName : dirNames) { + try { + Enumeration classLoaderURLs = classLoader.getResources(dirName); + while (classLoaderURLs.hasMoreElements()) { + URL url = classLoaderURLs.nextElement(); + urls.add(url); + } + } catch (IOException ioe) { + if (LOG.isErrorEnabled()) + LOG.error("Could not read driectory [#0]", ioe, dirName); + } + } + + return urls; + } + + private static Collection getUrls(ClassLoaderInterface classLoaderInterface, boolean excludeParent) throws IOException { + return getUrls(classLoaderInterface, excludeParent? classLoaderInterface.getParent() : null); + } + + private static Collection getUrls(ClassLoaderInterface classLoader, ClassLoaderInterface excludeParent) throws IOException { + UrlSet urlSet = new UrlSet(classLoader); + if (excludeParent != null){ + urlSet = urlSet.exclude(excludeParent); + } + return urlSet.getUrls(); + } + + private List file(URL location) { + List classNames = new ArrayList(); + File dir = new File(URLDecoder.decode(location.getPath())); + if ("META-INF".equals(dir.getName())) { + dir = dir.getParentFile(); // Scrape "META-INF" off + } + if (dir.isDirectory()) { + scanDir(dir, classNames, ""); + } + return classNames; + } + + private void scanDir(File dir, List classNames, String packageName) { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + scanDir(file, classNames, packageName + file.getName() + "."); + } else if (file.getName().endsWith(".class")) { + String name = file.getName(); + name = name.replaceFirst(".class$", ""); + classNames.add(packageName + name); + } + } + } + + private List jar(URL location) throws IOException { + URL url = URLUtil.normalizeToFileProtocol(location); + if (url != null) { + InputStream in = url.openStream(); + try { + JarInputStream jarStream = new JarInputStream(in); + return jar(jarStream); + } finally { + in.close(); + } + } else if (LOG.isDebugEnabled()) + LOG.debug("Unable to read [#0]", location.toExternalForm()); + + return Collections.emptyList(); + } + + private List jar(JarInputStream jarStream) throws IOException { + List classNames = new ArrayList(); + + JarEntry entry; + while ((entry = jarStream.getNextJarEntry()) != null) { + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + String className = entry.getName(); + className = className.replaceFirst(".class$", ""); + + //war files are treated as .jar files, so takeout WEB-INF/classes + className = StringUtils.removeStart(className, "WEB-INF/classes/"); + + className = className.replace('/', '.'); + classNames.add(className); + } + + return classNames; + } + + public class Annotatable { + private final List annotations = new ArrayList(); + + public Annotatable(AnnotatedElement element) { + for (Annotation annotation : element.getAnnotations()) { + annotations.add(new AnnotationInfo(annotation.annotationType().getName())); + } + } + + public Annotatable() { + } + + public List getAnnotations() { + return annotations; + } + + } + + public static interface Info { + String getName(); + + List getAnnotations(); + } + + public class PackageInfo extends Annotatable implements Info { + private final String name; + private final ClassInfo info; + private final Package pkg; + + public PackageInfo(Package pkg){ + super(pkg); + this.pkg = pkg; + this.name = pkg.getName(); + this.info = null; + } + + public PackageInfo(String name) { + info = new ClassInfo(name, null); + this.name = name; + this.pkg = null; + } + + public String getName() { + return name; + } + + public Package get() throws ClassNotFoundException { + return (pkg != null)?pkg:info.get().getPackage(); + } + } + + public class ClassInfo extends Annotatable implements Info { + private final String name; + private final List methods = new ArrayList(); + private final List constructors = new ArrayList(); + private final String superType; + private final List interfaces = new ArrayList(); + private final List superInterfaces = new ArrayList(); + private final List fields = new ArrayList(); + private Class clazz; + private ClassNotFoundException notFound; + + public ClassInfo(Class clazz) { + super(clazz); + this.clazz = clazz; + this.name = clazz.getName(); + Class superclass = clazz.getSuperclass(); + this.superType = superclass != null ? superclass.getName(): null; + } + + public ClassInfo(String name, String superType) { + this.name = name; + this.superType = superType; + } + + public String getPackageName(){ + return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "" ; + } + + public List getConstructors() { + return constructors; + } + + public List getInterfaces() { + return interfaces; + } + + public List getSuperInterfaces() { + return superInterfaces; + } + + public List getFields() { + return fields; + } + + public List getMethods() { + return methods; + } + + public String getName() { + return name; + } + + public String getSuperType() { + return superType; + } + + public Class get() throws ClassNotFoundException { + if (clazz != null) return clazz; + if (notFound != null) throw notFound; + try { + this.clazz = classLoaderInterface.loadClass(name); + return clazz; + } catch (ClassNotFoundException notFound) { + classesNotLoaded.add(name); + this.notFound = notFound; + throw notFound; + } + } + + @Override + public String toString() { + return name; + } + } + + public class MethodInfo extends Annotatable implements Info { + private final ClassInfo declaringClass; + private final String returnType; + private final String name; + private final List> parameterAnnotations = new ArrayList>(); + + public MethodInfo(ClassInfo info, Constructor constructor){ + super(constructor); + this.declaringClass = info; + this.name = ""; + this.returnType = Void.TYPE.getName(); + } + + public MethodInfo(ClassInfo info, Method method){ + super(method); + this.declaringClass = info; + this.name = method.getName(); + this.returnType = method.getReturnType().getName(); + } + + public MethodInfo(ClassInfo declarignClass, String name, String returnType) { + this.declaringClass = declarignClass; + this.name = name; + this.returnType = returnType; + } + + public List> getParameterAnnotations() { + return parameterAnnotations; + } + + public List getParameterAnnotations(int index) { + if (index >= parameterAnnotations.size()) { + for (int i = parameterAnnotations.size(); i <= index; i++) { + List annotationInfos = new ArrayList(); + parameterAnnotations.add(i, annotationInfos); + } + } + return parameterAnnotations.get(index); + } + + public String getName() { + return name; + } + + public ClassInfo getDeclaringClass() { + return declaringClass; + } + + public String getReturnType() { + return returnType; + } + + @Override + public String toString() { + return declaringClass + "@" + name; + } + } + + public class FieldInfo extends Annotatable implements Info { + private final String name; + private final String type; + private final ClassInfo declaringClass; + + public FieldInfo(ClassInfo info, Field field){ + super(field); + this.declaringClass = info; + this.name = field.getName(); + this.type = field.getType().getName(); + } + + public FieldInfo(ClassInfo declaringClass, String name, String type) { + this.declaringClass = declaringClass; + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public ClassInfo getDeclaringClass() { + return declaringClass; + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return declaringClass + "#" + name; + } + } + + public class AnnotationInfo extends Annotatable implements Info { + private final String name; + + public AnnotationInfo(Annotation annotation){ + this(annotation.getClass().getName()); + } + + public AnnotationInfo(Class annotation) { + this.name = annotation.getName().intern(); + } + + public AnnotationInfo(String name) { + name = name.replaceAll("^L|;$", ""); + name = name.replace('/', '.'); + this.name = name.intern(); + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + } + + private List getAnnotationInfos(String name) { + List infos = annotated.get(name); + if (infos == null) { + infos = new ArrayList(); + annotated.put(name, infos); + } + return infos; + } + + private void readClassDef(String className) { + if (!className.endsWith(".class")) { + className = className.replace('.', '/') + ".class"; + } + try { + URL resource = classLoaderInterface.getResource(className); + if (resource != null) { + InputStream in = resource.openStream(); + try { + ClassReader classReader = new ClassReader(in); + classReader.accept(new InfoBuildingVisitor(), ClassReader.SKIP_DEBUG); + } finally { + in.close(); + } + } else { + throw new XWorkException("Could not load " + className); + } + } catch (IOException e) { + throw new XWorkException("Could not load " + className, e); + } + + } + + public class InfoBuildingVisitor extends EmptyVisitor { + private Info info; + + public InfoBuildingVisitor() { + } + + public InfoBuildingVisitor(Info info) { + this.info = info; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (name.endsWith("package-info")) { + info = new PackageInfo(javaName(name)); + } else { + ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName)); + + for (String interfce : interfaces) { + classInfo.getInterfaces().add(javaName(interfce)); + } + info = classInfo; + classInfos.put(classInfo.getName(), classInfo); + + if (extractBaseInterfaces) + extractSuperInterfaces(classInfo); + } + } + + private void extractSuperInterfaces(ClassInfo classInfo) { + String superType = classInfo.getSuperType(); + + if (superType != null) { + ClassInfo base = classInfos.get(superType); + + if (base == null) { + //try to load base + String resource = superType.replace('.', '/') + ".class"; + readClassDef(resource); + base = classInfos.get(superType); + } + + if (base != null) { + List interfaces = classInfo.getSuperInterfaces(); + interfaces.addAll(base.getSuperInterfaces()); + interfaces.addAll(base.getInterfaces()); + } + } + } + + private String javaName(String name) { + return (name == null)? null:name.replace('/', '.'); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + info.getAnnotations().add(annotationInfo); + getAnnotationInfos(annotationInfo.getName()).add(info); + return new InfoBuildingVisitor(annotationInfo); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + ClassInfo classInfo = ((ClassInfo) info); + FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc); + classInfo.getFields().add(fieldInfo); + return new InfoBuildingVisitor(fieldInfo); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + ClassInfo classInfo = ((ClassInfo) info); + MethodInfo methodInfo = new MethodInfo(classInfo, name, desc); + classInfo.getMethods().add(methodInfo); + return new InfoBuildingVisitor(methodInfo); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) { + MethodInfo methodInfo = ((MethodInfo) info); + List annotationInfos = methodInfo.getParameterAnnotations(param); + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + annotationInfos.add(annotationInfo); + return new InfoBuildingVisitor(annotationInfo); + } + } + + private static final class DefaultClassnameFilterImpl implements Test { + public boolean test(String className) { + return true; + } + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java new file mode 100644 index 000000000..f9b4a0fbc --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java @@ -0,0 +1,41 @@ +/* + * 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.util.finder; + +import java.net.URL; +import java.util.Enumeration; +import java.io.IOException; +import java.io.InputStream; + +/** + * Classes implementing this interface can find resources and load classes, usually delegating to a class + * loader + */ +public interface ClassLoaderInterface { + + //key used to add the current ClassLoaderInterface to ActionContext + public final String CLASS_LOADER_INTERFACE = "__current_class_loader_interface"; + + Class loadClass(String name) throws ClassNotFoundException; + + URL getResource(String name); + + public Enumeration getResources(String name) throws IOException; + + public InputStream getResourceAsStream(String name) throws IOException; + + ClassLoaderInterface getParent(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java new file mode 100644 index 000000000..79fa46084 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java @@ -0,0 +1,52 @@ +/* + * 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.util.finder; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** + * Default implementation of ClassLoaderInterface, which delegates to an actual ClassLoader + */ +public class ClassLoaderInterfaceDelegate implements ClassLoaderInterface { + private ClassLoader classLoader; + + public ClassLoaderInterfaceDelegate(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public Class loadClass(String name) throws ClassNotFoundException { + return classLoader.loadClass(name); + } + + public URL getResource(String className) { + return classLoader.getResource(className); + } + + public Enumeration getResources(String name) throws IOException { + return classLoader.getResources(name); + } + + public InputStream getResourceAsStream(String name) { + return classLoader.getResourceAsStream(name); + } + + public ClassLoaderInterface getParent() { + return classLoader.getParent() != null ? new ClassLoaderInterfaceDelegate(classLoader.getParent()) : null; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java new file mode 100644 index 000000000..28a146e98 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java @@ -0,0 +1,1153 @@ +/* + * 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.util.finder; + +import org.apache.commons.lang.StringUtils; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + * @author David Blevins + * @version $Rev$ $Date$ + */ +public class ResourceFinder { + private static final Logger LOG = LoggerFactory.getLogger(ResourceFinder.class); + + private final URL[] urls; + private final String path; + private final ClassLoaderInterface classLoaderInterface; + private final List resourcesNotLoaded = new ArrayList(); + + public ResourceFinder(URL... urls) { + this(null, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), urls); + } + + public ResourceFinder(String path) { + this(path, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), null); + } + + public ResourceFinder(String path, URL... urls) { + this(path, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), urls); + } + + public ResourceFinder(String path, ClassLoaderInterface classLoaderInterface) { + this(path, classLoaderInterface, null); + } + + public ResourceFinder(String path, ClassLoaderInterface classLoaderInterface, URL... urls) { + if (path == null){ + path = ""; + } else if (path.length() > 0 && !path.endsWith("/")) { + path += "/"; + } + this.path = path; + + this.classLoaderInterface = classLoaderInterface == null ? new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()) : classLoaderInterface ; + + for (int i = 0; urls != null && i < urls.length; i++) { + URL url = urls[i]; + if (url == null || isDirectory(url) || "jar".equals(url.getProtocol())) { + continue; + } + try { + urls[i] = new URL("jar", "", -1, url.toString() + "!/"); + } catch (MalformedURLException e) { + } + } + this.urls = (urls == null || urls.length == 0)? null : urls; + } + + private static boolean isDirectory(URL url) { + String file = url.getFile(); + return (file.length() > 0 && file.charAt(file.length() - 1) == '/'); + } + + /** + * Returns a list of resources that could not be loaded in the last invoked findAvailable* or + * mapAvailable* methods. + *

+ * The list will only contain entries of resources that match the requirements + * of the last invoked findAvailable* or mapAvailable* methods, but were unable to be + * loaded and included in their results. + *

+ * The list returned is unmodifiable and the results of this method will change + * after each invocation of a findAvailable* or mapAvailable* methods. + *

+ * This method is not thread safe. + */ + public List getResourcesNotLoaded() { + return Collections.unmodifiableList(resourcesNotLoaded); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + public URL find(String uri) throws IOException { + String fullUri = path + uri; + + return getResource(fullUri); + } + + public List findAll(String uri) throws IOException { + String fullUri = path + uri; + + Enumeration resources = getResources(fullUri); + List list = new ArrayList(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + list.add(url); + } + return list; + } + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find String + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Reads the contents of the URL as a {@link String}'s and returns it. + * + * @param uri + * @return a stringified content of a resource + * @throws IOException if a resource pointed out by the uri param could not be find + * @see ClassLoader#getResource(String) + */ + public String findString(String uri) throws IOException { + String fullUri = path + uri; + + URL resource = getResource(fullUri); + if (resource == null) { + throw new IOException("Could not find a resource in : " + fullUri); + } + + return readContents(resource); + } + + /** + * Reads the contents of the found URLs as a list of {@link String}'s and returns them. + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if any of the found URLs are unable to be read. + */ + public List findAllStrings(String uri) throws IOException { + String fulluri = path + uri; + + List strings = new ArrayList(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + String string = readContents(url); + strings.add(string); + } + return strings; + } + + /** + * Reads the contents of the found URLs as a Strings and returns them. + * Individual URLs that cannot be read are skipped and added to the + * list of 'resourcesNotLoaded' + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableStrings(String uri) throws IOException { + resourcesNotLoaded.clear(); + String fulluri = path + uri; + + List strings = new ArrayList(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + try { + String string = readContents(url); + strings.add(string); + } catch (IOException notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return strings; + } + + /** + * Reads the contents of all non-directory URLs immediately under the specified + * location and returns them in a map keyed by the file name. + *

+ * Any URLs that cannot be read will cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/serializables/one + * META-INF/serializables/two + * META-INF/serializables/three + * META-INF/serializables/four/foo.txt + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("serializables"); + * map.contains("one"); // true + * map.contains("two"); // true + * map.contains("three"); // true + * map.contains("four"); // false + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if any of the urls cannot be read + */ + public Map mapAllStrings(String uri) throws IOException { + Map strings = new HashMap(); + Map resourcesMap = getResourcesMap(uri); + for (Map.Entry entry : resourcesMap.entrySet()) { + String name = entry.getKey(); + URL url = entry.getValue(); + String value = readContents(url); + strings.put(name, value); + } + return strings; + } + + /** + * Reads the contents of all non-directory URLs immediately under the specified + * location and returns them in a map keyed by the file name. + *

+ * Individual URLs that cannot be read are skipped and added to the + * list of 'resourcesNotLoaded' + *

+ * Example classpath: + *

+ * META-INF/serializables/one + * META-INF/serializables/two # not readable + * META-INF/serializables/three + * META-INF/serializables/four/foo.txt + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("serializables"); + * map.contains("one"); // true + * map.contains("two"); // false + * map.contains("three"); // true + * map.contains("four"); // false + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableStrings(String uri) throws IOException { + resourcesNotLoaded.clear(); + Map strings = new HashMap(); + Map resourcesMap = getResourcesMap(uri); + for (Map.Entry entry : resourcesMap.entrySet()) { + String name = entry.getKey(); + URL url = entry.getValue(); + try { + String value = readContents(url); + strings.put(name, value); + } catch (IOException notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return strings; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find Class + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Executes {@link #findString(String)} assuming the contents URL found is the name of + * a class that should be loaded and returned. + * + * @param uri + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public Class findClass(String uri) throws IOException, ClassNotFoundException { + String className = findString(uri); + return (Class) classLoaderInterface.loadClass(className); + } + + /** + * Executes findAllStrings assuming the strings are + * the names of a classes that should be loaded and returned. + *

+ * Any URL or class that cannot be loaded will cause an exception to be thrown. + * + * @param uri + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public List findAllClasses(String uri) throws IOException, ClassNotFoundException { + List classes = new ArrayList(); + List strings = findAllStrings(uri); + for (String className : strings) { + Class clazz = classLoaderInterface.loadClass(className); + classes.add(clazz); + } + return classes; + } + + /** + * Executes findAvailableStrings assuming the strings are + * the names of a classes that should be loaded and returned. + *

+ * Any class that cannot be loaded will be skipped and placed in the + * 'resourcesNotLoaded' collection. + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableClasses(String uri) throws IOException { + resourcesNotLoaded.clear(); + List classes = new ArrayList(); + List strings = findAvailableStrings(uri); + for (String className : strings) { + try { + Class clazz = classLoaderInterface.loadClass(className); + classes.add(clazz); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return classes; + } + + /** + * Executes mapAllStrings assuming the value of each entry in the + * map is the name of a class that should be loaded. + *

+ * Any class that cannot be loaded will be cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/xmlparsers/xerces + * META-INF/xmlparsers/crimson + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("xmlparsers"); + * map.contains("xerces"); // true + * map.contains("crimson"); // true + * Class xercesClass = map.get("xerces"); + * Class crimsonClass = map.get("crimson"); + * + * @param uri + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public Map mapAllClasses(String uri) throws IOException, ClassNotFoundException { + Map classes = new HashMap(); + Map map = mapAllStrings(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + Class clazz = classLoaderInterface.loadClass(className); + classes.put(string, clazz); + } + return classes; + } + + /** + * Executes mapAvailableStrings assuming the value of each entry in the + * map is the name of a class that should be loaded. + *

+ * Any class that cannot be loaded will be skipped and placed in the + * 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/xmlparsers/xerces + * META-INF/xmlparsers/crimson + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("xmlparsers"); + * map.contains("xerces"); // true + * map.contains("crimson"); // true + * Class xercesClass = map.get("xerces"); + * Class crimsonClass = map.get("crimson"); + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableClasses(String uri) throws IOException { + resourcesNotLoaded.clear(); + Map classes = new HashMap(); + Map map = mapAvailableStrings(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + try { + Class clazz = classLoaderInterface.loadClass(className); + classes.put(string, clazz); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return classes; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find Implementation + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Assumes the class specified points to a file in the classpath that contains + * the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded will be cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream + * META-INF/java.io.OutputStream + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Class clazz = finder.findImplementation(java.io.InputStream.class); + * clazz.getName(); // returns "org.acme.AcmeInputStream" + * + * @param interfase a superclass or interface + * @return + * @throws IOException if the URL cannot be read + * @throws ClassNotFoundException if the class found is not loadable + * @throws ClassCastException if the class found is not assignable to the specified superclass or interface + */ + public Class findImplementation(Class interfase) throws IOException, ClassNotFoundException { + String className = findString(interfase.getName()); + Class impl = classLoaderInterface.loadClass(className); + if (!interfase.isAssignableFrom(impl)) { + throw new ClassCastException("Class not of type: " + interfase.getName()); + } + return impl; + } + + /** + * Assumes the class specified points to a file in the classpath that contains + * the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or assigned to the specified interface will be cause + * an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream + * META-INF/java.io.InputStream # contains the classname org.widget.NeatoInputStream + * META-INF/java.io.InputStream # contains the classname com.foo.BarInputStream + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List classes = finder.findAllImplementations(java.io.InputStream.class); + * classes.contains("org.acme.AcmeInputStream"); // true + * classes.contains("org.widget.NeatoInputStream"); // true + * classes.contains("com.foo.BarInputStream"); // true + * + * @param interfase a superclass or interface + * @return + * @throws IOException if the URL cannot be read + * @throws ClassNotFoundException if the class found is not loadable + * @throws ClassCastException if the class found is not assignable to the specified superclass or interface + */ + public List findAllImplementations(Class interfase) throws IOException, ClassNotFoundException { + List implementations = new ArrayList(); + List strings = findAllStrings(interfase.getName()); + for (String className : strings) { + Class impl = classLoaderInterface.loadClass(className); + if (!interfase.isAssignableFrom(impl)) { + throw new ClassCastException("Class not of type: " + interfase.getName()); + } + implementations.add(impl); + } + return implementations; + } + + /** + * Assumes the class specified points to a file in the classpath that contains + * the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or are not assignable to the specified class will be + * skipped and placed in the 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream + * META-INF/java.io.InputStream # contains the classname org.widget.NeatoInputStream + * META-INF/java.io.InputStream # contains the classname com.foo.BarInputStream + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List classes = finder.findAllImplementations(java.io.InputStream.class); + * classes.contains("org.acme.AcmeInputStream"); // true + * classes.contains("org.widget.NeatoInputStream"); // true + * classes.contains("com.foo.BarInputStream"); // true + * + * @param interfase a superclass or interface + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableImplementations(Class interfase) throws IOException { + resourcesNotLoaded.clear(); + List implementations = new ArrayList(); + List strings = findAvailableStrings(interfase.getName()); + for (String className : strings) { + try { + Class impl = classLoaderInterface.loadClass(className); + if (interfase.isAssignableFrom(impl)) { + implementations.add(impl); + } else { + resourcesNotLoaded.add(className); + } + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return implementations; + } + + /** + * Assumes the class specified points to a directory in the classpath that holds files + * containing the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or assigned to the specified interface will be cause + * an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/java.net.URLStreamHandler/jar + * META-INF/java.net.URLStreamHandler/file + * META-INF/java.net.URLStreamHandler/http + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class); + * Class jarUrlHandler = map.get("jar"); + * Class fileUrlHandler = map.get("file"); + * Class httpUrlHandler = map.get("http"); + * + * @param interfase a superclass or interface + * @return + * @throws IOException if the URL cannot be read + * @throws ClassNotFoundException if the class found is not loadable + * @throws ClassCastException if the class found is not assignable to the specified superclass or interface + */ + public Map mapAllImplementations(Class interfase) throws IOException, ClassNotFoundException { + Map implementations = new HashMap(); + Map map = mapAllStrings(interfase.getName()); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + Class impl = classLoaderInterface.loadClass(className); + if (!interfase.isAssignableFrom(impl)) { + throw new ClassCastException("Class not of type: " + interfase.getName()); + } + implementations.put(string, impl); + } + return implementations; + } + + /** + * Assumes the class specified points to a directory in the classpath that holds files + * containing the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or are not assignable to the specified class will be + * skipped and placed in the 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/java.net.URLStreamHandler/jar + * META-INF/java.net.URLStreamHandler/file + * META-INF/java.net.URLStreamHandler/http + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class); + * Class jarUrlHandler = map.get("jar"); + * Class fileUrlHandler = map.get("file"); + * Class httpUrlHandler = map.get("http"); + * + * @param interfase a superclass or interface + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableImplementations(Class interfase) throws IOException { + resourcesNotLoaded.clear(); + Map implementations = new HashMap(); + Map map = mapAvailableStrings(interfase.getName()); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + try { + Class impl = classLoaderInterface.loadClass(className); + if (interfase.isAssignableFrom(impl)) { + implementations.put(string, impl); + } else { + resourcesNotLoaded.add(className); + } + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return implementations; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find Properties + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Finds the corresponding resource and reads it in as a properties file + *

+ * Example classpath: + *

+ * META-INF/widget.properties + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Properties widgetProps = finder.findProperties("widget.properties"); + * + * @param uri + * @return + * @throws IOException if the URL cannot be read or is not in properties file format + */ + public Properties findProperties(String uri) throws IOException { + String fulluri = path + uri; + + URL resource = getResource(fulluri); + if (resource == null) { + throw new IOException("Could not find command in : " + fulluri); + } + + return loadProperties(resource); + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/app.properties + * META-INF/app.properties + * META-INF/app.properties + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List appProps = finder.findAllProperties("app.properties"); + * + * @param uri + * @return + * @throws IOException if the URL cannot be read or is not in properties file format + */ + public List findAllProperties(String uri) throws IOException { + String fulluri = path + uri; + + List properties = new ArrayList(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + Properties props = loadProperties(url); + properties.add(props); + } + return properties; + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will be added to the + * 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/app.properties + * META-INF/app.properties + * META-INF/app.properties + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List appProps = finder.findAvailableProperties("app.properties"); + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableProperties(String uri) throws IOException { + resourcesNotLoaded.clear(); + String fulluri = path + uri; + + List properties = new ArrayList(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + try { + Properties props = loadProperties(url); + properties.add(props); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return properties; + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/jdbcDrivers/oracle.properties + * META-INF/jdbcDrivers/mysql.props + * META-INF/jdbcDrivers/derby + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List driversList = finder.findAvailableProperties("jdbcDrivers"); + * Properties oracleProps = driversList.get("oracle.properties"); + * Properties mysqlProps = driversList.get("mysql.props"); + * Properties derbyProps = driversList.get("derby"); + * + * @param uri + * @return + * @throws IOException if the URL cannot be read or is not in properties file format + */ + public Map mapAllProperties(String uri) throws IOException { + Map propertiesMap = new HashMap(); + Map map = getResourcesMap(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + URL url = entry.getValue(); + Properties properties = loadProperties(url); + propertiesMap.put(string, properties); + } + return propertiesMap; + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will be added to the + * 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/jdbcDrivers/oracle.properties + * META-INF/jdbcDrivers/mysql.props + * META-INF/jdbcDrivers/derby + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List driversList = finder.findAvailableProperties("jdbcDrivers"); + * Properties oracleProps = driversList.get("oracle.properties"); + * Properties mysqlProps = driversList.get("mysql.props"); + * Properties derbyProps = driversList.get("derby"); + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableProperties(String uri) throws IOException { + resourcesNotLoaded.clear(); + Map propertiesMap = new HashMap(); + Map map = getResourcesMap(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + URL url = entry.getValue(); + try { + Properties properties = loadProperties(url); + propertiesMap.put(string, properties); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return propertiesMap; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Map Resources + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + public Map getResourcesMap(String uri) throws IOException { + String basePath = path + uri; + + Map resources = new HashMap(); + if (!basePath.endsWith("/")) { + basePath += "/"; + } + Enumeration urls = getResources(basePath); + + while (urls.hasMoreElements()) { + URL location = urls.nextElement(); + + try { + if ("jar".equals(location.getProtocol())) { + + readJarEntries(location, basePath, resources); + + } else if ("file".equals(location.getProtocol())) { + + readDirectoryEntries(location, resources); + + } + } catch (Exception e) { + } + } + + return resources; + } + + /** + * Gets a list of subpckages from jars or dirs + */ + public Set findPackages(String uri) throws IOException { + String basePath = path + uri; + + Set resources = new HashSet(); + if (!basePath.endsWith("/")) { + basePath += "/"; + } + Enumeration urls = getResources(basePath); + + while (urls.hasMoreElements()) { + URL location = urls.nextElement(); + + try { + if ("jar".equals(location.getProtocol())) { + + readJarDirectoryEntries(location, basePath, resources); + + } else if ("file".equals(location.getProtocol())) { + + readSubDirectories(new File(location.toURI()), uri, resources); + + } + } catch (Exception e) { + } + } + + return convertPathsToPackages(resources); + } + + /** + * Gets a list of subpckages from jars or dirs + */ + public Map> findPackagesMap(String uri) throws IOException { + String basePath = path + uri; + + if (!basePath.endsWith("/")) { + basePath += "/"; + } + Enumeration urls = getResources(basePath); + Map> result = new HashMap>(); + + while (urls.hasMoreElements()) { + URL location = urls.nextElement(); + + try { + if ("jar".equals(location.getProtocol())) { + Set resources = new HashSet(); + readJarDirectoryEntries(location, basePath, resources); + result.put(location, convertPathsToPackages(resources)); + } else if ("file".equals(location.getProtocol())) { + Set resources = new HashSet(); + readSubDirectories(new File(location.toURI()), uri, resources); + result.put(location, convertPathsToPackages(resources)); + } + } catch (Exception e) { + } + } + + return result; + } + + private Set convertPathsToPackages(Set resources) { + Set packageNames = new HashSet(resources.size()); + for(String resource : resources) { + packageNames.add(StringUtils.chomp(StringUtils.replace(resource, "/", "."), ".")); + } + + return packageNames; + } + + private static void readDirectoryEntries(URL location, Map resources) throws MalformedURLException { + File dir = new File(URLDecoder.decode(location.getPath())); + if (dir.isDirectory()) { + File[] files = dir.listFiles(); + for (File file : files) { + if (!file.isDirectory()) { + String name = file.getName(); + URL url = file.toURL(); + resources.put(name, url); + } + } + } + } + + /** + * Reads subdirectories of a file. The output is a list of subdirectories, relative to the basepath + */ + private static void readSubDirectories(File dir, String basePath, Set resources) throws MalformedURLException { + if (dir.isDirectory()) { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + String name = file.getName(); + String subName = StringUtils.chomp(basePath, "/") + "/" + name; + resources.add(subName); + readSubDirectories(file, subName, resources); + } + } + } + } + + private static void readJarEntries(URL location, String basePath, Map resources) throws IOException { + JarURLConnection conn = (JarURLConnection) location.openConnection(); + JarFile jarfile = null; + jarfile = conn.getJarFile(); + + Enumeration entries = jarfile.entries(); + while (entries != null && entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (entry.isDirectory() || !name.startsWith(basePath) || name.length() == basePath.length()) { + continue; + } + + name = name.substring(basePath.length()); + + if (name.contains("/")) { + continue; + } + + URL resource = new URL(location, name); + resources.put(name, resource); + } + } + + //read directories in the jar that start with the basePath + private static void readJarDirectoryEntries(URL location, String basePath, Set resources) throws IOException { + JarURLConnection conn = (JarURLConnection) location.openConnection(); + JarFile jarfile = null; + jarfile = conn.getJarFile(); + + Enumeration entries = jarfile.entries(); + while (entries != null && entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (entry.isDirectory() && StringUtils.startsWith(name, basePath)) { + resources.add(name); + } + } + } + + private Properties loadProperties(URL resource) throws IOException { + InputStream in = resource.openStream(); + + BufferedInputStream reader = null; + try { + reader = new BufferedInputStream(in); + Properties properties = new Properties(); + properties.load(reader); + + return properties; + } finally { + try { + in.close(); + reader.close(); + } catch (Exception e) { + } + } + } + + private String readContents(URL resource) throws IOException { + InputStream in = resource.openStream(); + BufferedInputStream reader = null; + StringBuilder sb = new StringBuilder(); + + try { + reader = new BufferedInputStream(in); + + int b = reader.read(); + while (b != -1) { + sb.append((char) b); + b = reader.read(); + } + + return sb.toString().trim(); + } finally { + try { + in.close(); + reader.close(); + } catch (Exception e) { + } + } + } + + private URL getResource(String fullUri) { + if (urls == null){ + return classLoaderInterface.getResource(fullUri); + } + return findResource(fullUri, urls); + } + + private Enumeration getResources(String fulluri) throws IOException { + if (urls == null) { + return classLoaderInterface.getResources(fulluri); + } + Vector resources = new Vector(); + for (URL url : urls) { + URL resource = findResource(fulluri, url); + if (resource != null){ + resources.add(resource); + } + } + return resources.elements(); + } + + private URL findResource(String resourceName, URL... search) { + for (int i = 0; i < search.length; i++) { + URL currentUrl = search[i]; + if (currentUrl == null) { + continue; + } + JarFile jarFile = null; + try { + String protocol = currentUrl.getProtocol(); + if ("jar".equals(protocol)) { + /* + * If the connection for currentUrl or resURL is + * used, getJarFile() will throw an exception if the + * entry doesn't exist. + */ + URL jarURL = ((JarURLConnection) currentUrl.openConnection()).getJarFileURL(); + try { + JarURLConnection juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection(); + jarFile = juc.getJarFile(); + } catch (IOException e) { + // Don't look for this jar file again + search[i] = null; + throw e; + } + + String entryName; + if (currentUrl.getFile().endsWith("!/")) { + entryName = resourceName; + } else { + String file = currentUrl.getFile(); + int sepIdx = file.lastIndexOf("!/"); + if (sepIdx == -1) { + // Invalid URL, don't look here again + search[i] = null; + continue; + } + sepIdx += 2; + StringBuilder sb = new StringBuilder(file.length() - sepIdx + resourceName.length()); + sb.append(file.substring(sepIdx)); + sb.append(resourceName); + entryName = sb.toString(); + } + if ("META-INF/".equals(entryName) && jarFile.getEntry("META-INF/MANIFEST.MF") != null){ + return targetURL(currentUrl, "META-INF/MANIFEST.MF"); + } + if (jarFile.getEntry(entryName) != null) { + return targetURL(currentUrl, resourceName); + } + } else if ("file".equals(protocol)) { + String baseFile = currentUrl.getFile(); + String host = currentUrl.getHost(); + int hostLength = 0; + if (host != null) { + hostLength = host.length(); + } + StringBuilder buf = new StringBuilder(2 + hostLength + baseFile.length() + resourceName.length()); + + if (hostLength > 0) { + buf.append("//").append(host); + } + // baseFile always ends with '/' + buf.append(baseFile); + String fixedResName = resourceName; + // Do not create a UNC path, i.e. \\host + while (fixedResName.startsWith("/") || fixedResName.startsWith("\\")) { + fixedResName = fixedResName.substring(1); + } + buf.append(fixedResName); + String filename = buf.toString(); + File file = new File(filename); + File file2 = new File(URLDecoder.decode(filename)); + + if (file.exists() || file2.exists()) { + return targetURL(currentUrl, fixedResName); + } + } else { + URL resourceURL = targetURL(currentUrl, resourceName); + URLConnection urlConnection = resourceURL.openConnection(); + + try { + urlConnection.getInputStream().close(); + } catch (SecurityException e) { + return null; + } + // HTTP can return a stream on a non-existent file + // So check for the return code; + if (!"http".equals(resourceURL.getProtocol())) { + return resourceURL; + } + + int code = ((HttpURLConnection) urlConnection).getResponseCode(); + if (code >= 200 && code < 300) { + return resourceURL; + } + } + } catch (MalformedURLException e) { + // Keep iterating through the URL list + } catch (IOException e) { + } catch (SecurityException e) { + } + } + return null; + } + + private URL targetURL(URL base, String name) throws MalformedURLException { + StringBuilder sb = new StringBuilder(base.getFile().length() + name.length()); + sb.append(base.getFile()); + sb.append(name); + String file = sb.toString(); + return new URL(base.getProtocol(), base.getHost(), base.getPort(), file, null); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java new file mode 100644 index 000000000..b78d6f4b6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java @@ -0,0 +1,29 @@ +/* + * 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.util.finder; + +/** + * This is the testing interface that is used to accept or reject resources. + */ +public interface Test { + /** + * The test method. + * + * @param t The resource object to test. + * @return True if the resource should be accepted, false otherwise. + */ + public boolean test(T t); +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java new file mode 100644 index 000000000..d8b7cdeaa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java @@ -0,0 +1,266 @@ +/* + * 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.util.finder; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.util.URLUtil; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.ObjectUtils; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; + +/** + * Use with ClassFinder to filter the Urls to be scanned, example: + *

+ * UrlSet urlSet = new UrlSet(classLoader);
+ * urlSet = urlSet.exclude(ClassLoader.getSystemClassLoader().getParent());
+ * urlSet = urlSet.excludeJavaExtDirs();
+ * urlSet = urlSet.excludeJavaEndorsedDirs();
+ * urlSet = urlSet.excludeJavaHome();
+ * urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
+ * urlSet = urlSet.exclude(".*?/JavaVM.framework/.*");
+ * urlSet = urlSet.exclude(".*?/activemq-(core|ra)-[\\d.]+.jar(!/)?");
+ * 
+ * @author David Blevins + * @version $Rev$ $Date$ + */ +public class UrlSet { + private static final Logger LOG = LoggerFactory.getLogger(UrlSet.class); + private final Map urls; + private Set protocols; + + + public UrlSet(ClassLoaderInterface classLoader) throws IOException { + this(getUrls(classLoader)); + } + + public UrlSet(ClassLoaderInterface classLoader, Set protocols) throws IOException { + this(getUrls(classLoader, protocols)); + this.protocols = protocols; + } + + public UrlSet(URL... urls){ + this(Arrays.asList(urls)); + } + /** + * Ignores all URLs that are not "jar" or "file" + * @param urls + */ + public UrlSet(Collection urls){ + this.urls = new HashMap(); + for (URL location : urls) { + try { +// if (location.getProtocol().equals("file")) { +// try { +// // See if it's actually a jar +// URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); +// JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); +// juc.getJarFile(); +// location = jarUrl; +// } catch (IOException e) { +// } +// this.urls.put(location.toExternalForm(), location); +// } + this.urls.put(location.toExternalForm(), location); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private UrlSet(Map urls) { + this.urls = urls; + } + + public UrlSet include(UrlSet urlSet){ + Map urls = new HashMap(this.urls); + urls.putAll(urlSet.urls); + return new UrlSet(urls); + } + + public UrlSet exclude(UrlSet urlSet) { + Map urls = new HashMap(this.urls); + Map parentUrls = urlSet.urls; + for (String url : parentUrls.keySet()) { + urls.remove(url); + } + return new UrlSet(urls); + } + + public UrlSet exclude(ClassLoaderInterface parent) throws IOException { + return exclude(new UrlSet(parent, this.protocols)); + } + + public UrlSet exclude(File file) throws MalformedURLException { + return exclude(relative(file)); + } + + public UrlSet exclude(String pattern) throws MalformedURLException { + return exclude(matching(pattern)); + } + + /** + * Calls excludePaths(System.getProperty("java.ext.dirs")) + * @return + * @throws MalformedURLException + */ + public UrlSet excludeJavaExtDirs() throws MalformedURLException { + return excludePaths(System.getProperty("java.ext.dirs", "")); + } + + /** + * Calls excludePaths(System.getProperty("java.endorsed.dirs")) + * + * @return + * @throws MalformedURLException + */ + public UrlSet excludeJavaEndorsedDirs() throws MalformedURLException { + return excludePaths(System.getProperty("java.endorsed.dirs", "")); + } + + public UrlSet excludeJavaHome() throws MalformedURLException { + String path = System.getProperty("java.home"); + if (path != null) { + + File java = new File(path); + + if (path.matches("/System/Library/Frameworks/JavaVM.framework/Versions/[^/]+/Home")){ + java = java.getParentFile(); + } + return exclude(java); + } else { + return this; + } + } + + public UrlSet excludePaths(String pathString) throws MalformedURLException { + String[] paths = pathString.split(File.pathSeparator); + UrlSet urlSet = this; + for (String path : paths) { + if (StringUtils.isNotEmpty(path)) { + File file = new File(path); + urlSet = urlSet.exclude(file); + } + } + return urlSet; + } + + public UrlSet matching(String pattern) { + Map urls = new HashMap(); + for (Map.Entry entry : this.urls.entrySet()) { + String url = entry.getKey(); + if (url.matches(pattern)){ + urls.put(url, entry.getValue()); + } + } + return new UrlSet(urls); + } + + /** + * Try to find a classes directory inside a war file add its normalized url to this set + */ + public UrlSet includeClassesUrl(ClassLoaderInterface classLoaderInterface) throws IOException { + Enumeration rootUrlEnumeration = classLoaderInterface.getResources(""); + while (rootUrlEnumeration.hasMoreElements()) { + URL url = rootUrlEnumeration.nextElement(); + String externalForm = StringUtils.removeEnd(url.toExternalForm(), "/"); + if (externalForm.endsWith(".war/WEB-INF/classes")) { + //if it is inside a war file, get the url to the file + externalForm = StringUtils.substringBefore(externalForm, "/WEB-INF/classes"); + URL warUrl = new URL(externalForm); + URL normalizedUrl = URLUtil.normalizeToFileProtocol(warUrl); + URL finalUrl = (URL) ObjectUtils.defaultIfNull(normalizedUrl, warUrl); + + Map newUrls = new HashMap(this.urls); + newUrls.put(finalUrl.toExternalForm(), finalUrl); + return new UrlSet(newUrls); + } + } + + return this; + } + + public UrlSet relative(File file) throws MalformedURLException { + String urlPath = file.toURL().toExternalForm(); + Map urls = new HashMap(); + for (Map.Entry entry : this.urls.entrySet()) { + String url = entry.getKey(); + if (url.startsWith(urlPath) || url.startsWith("jar:"+urlPath)){ + urls.put(url, entry.getValue()); + } + } + return new UrlSet(urls); + } + + public List getUrls() { + return new ArrayList(urls.values()); + } + + private static List getUrls(ClassLoaderInterface classLoader) throws IOException { + List list = new ArrayList(); + + //find jars + ArrayList urls = Collections.list(classLoader.getResources("META-INF")); + + for (URL url : urls) { + if ("jar".equalsIgnoreCase(url.getProtocol())) { + String externalForm = url.toExternalForm(); + //build a URL pointing to the jar, instead of the META-INF dir + url = new URL(StringUtils.substringBefore(externalForm, "META-INF")); + list.add(url); + } else if (LOG.isDebugEnabled()) + LOG.debug("Ignoring URL [#0] because it is not a jar", url.toExternalForm()); + + } + + //usually the "classes" dir + list.addAll(Collections.list(classLoader.getResources(""))); + return list; + } + + private static List getUrls(ClassLoaderInterface classLoader, Set protocols) throws IOException { + + if (protocols == null) { + return getUrls(classLoader); + } + + List list = new ArrayList(); + + //find jars + ArrayList urls = Collections.list(classLoader.getResources("META-INF")); + + for (URL url : urls) { + if (protocols.contains(url.getProtocol())) { + String externalForm = url.toExternalForm(); + //build a URL pointing to the jar, instead of the META-INF dir + url = new URL(StringUtils.substringBefore(externalForm, "META-INF")); + list.add(url); + } else if (LOG.isDebugEnabled()) + LOG.debug("Ignoring URL [#0] because it is not a valid protocol", url.toExternalForm()); + + } + + //usually the "classes" dir + list.addAll(Collections.list(classLoader.getResources(""))); + return list; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java new file mode 100644 index 000000000..fc6f69cd1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java @@ -0,0 +1,29 @@ +/* + * Copyright 2005 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.util.location; + +/** + * A interface that should be implemented by objects knowning their location (i.e. where they + * have been created from). + */ +public interface Locatable { + /** + * Get the location of this object + * + * @return the location + */ + public Location getLocation(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java new file mode 100644 index 000000000..99cde0e3d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java @@ -0,0 +1,76 @@ +package com.opensymphony.xwork2.util.location; + +import com.opensymphony.xwork2.util.PropertiesReader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * Properties implementation that remembers the location of each property. When + * loaded, a custom properties file parser is used to remember both the line number + * and preceeding comments for each property entry. + */ +public class LocatableProperties extends Properties implements Locatable { + + Location location; + Map propLocations; + + public LocatableProperties() { + this(null); + } + + public LocatableProperties(Location loc) { + super(); + this.location = loc; + this.propLocations = new HashMap(); + } + + @Override + public void load(InputStream in) throws IOException { + Reader reader = new InputStreamReader(in); + PropertiesReader pr = new PropertiesReader(reader); + while (pr.nextProperty()) { + String name = pr.getPropertyName(); + String val = pr.getPropertyValue(); + int line = pr.getLineNumber(); + String desc = convertCommentsToString(pr.getCommentLines()); + + Location loc = new LocationImpl(desc, location.getURI(), line, 0); + setProperty(name, val, loc); + } + } + + String convertCommentsToString(List lines) { + StringBuilder sb = new StringBuilder(); + if (lines != null && lines.size() > 0) { + for (String line : lines) { + sb.append(line).append('\n'); + } + } + return sb.toString(); + } + + public Object setProperty(String key, String value, Object locationObj) { + Object obj = super.setProperty(key, value); + if (location != null) { + Location loc = LocationUtils.getLocation(locationObj); + propLocations.put(key, loc); + } + return obj; + } + + public Location getPropertyLocation(String key) { + return propLocations.get(key); + } + + public Location getLocation() { + return location; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Located.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Located.java new file mode 100644 index 000000000..7c2c795ed --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Located.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005 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.util.location; + +/** + * Base class for location aware objects + */ +public abstract class Located implements Locatable { + + protected Location location; + + /** + * Get the location of this object + * + * @return the location + */ + public Location getLocation() { + return location; + } + + /** + * Set the location of this object + * + * @param loc the location + */ + public void setLocation(Location loc) { + this.location = loc; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Location.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Location.java new file mode 100644 index 000000000..7791d4fd2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/Location.java @@ -0,0 +1,69 @@ +/* + * Copyright 2005 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.util.location; + +import java.util.List; + + +/** + * A location in a resource. The location is composed of the URI of the resource, and + * the line and column numbers within that resource (when available), along with a description. + *

+ * Locations are mostly provided by {@link Locatable}s objects. + */ +public interface Location { + + /** + * Constant for unknown locations. + */ + public static final Location UNKNOWN = LocationImpl.UNKNOWN; + + /** + * Get the description of this location + * + * @return the description (can be null) + */ + String getDescription(); + + /** + * Get the URI of this location + * + * @return the URI (null if unknown). + */ + String getURI(); + + /** + * Get the line number of this location + * + * @return the line number (-1 if unknown) + */ + int getLineNumber(); + + /** + * Get the column number of this location + * + * @return the column number (-1 if unknown) + */ + int getColumnNumber(); + + /** + * Gets a source code snippet with the default padding + * + * @param padding The amount of lines before and after the error to include + * @return A list of source lines + */ + List getSnippet(int padding); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationAttributes.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationAttributes.java new file mode 100644 index 000000000..0bc507922 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationAttributes.java @@ -0,0 +1,348 @@ +/* + * Copyright 2005 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.util.location; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * A class to handle location information stored in attributes. + * These attributes are typically setup using {@link com.opensymphony.xwork2.util.location.LocationAttributes.Pipe} + * which augments the SAX stream with additional attributes, e.g.: + *

+ * <root xmlns:loc="http://opensymphony.com/xwork/location"
+ *       loc:src="file://path/to/file.xml"
+ *       loc:line="1" loc:column="1">
+ *   <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
+ * </root>
+ * 
+ * + * @see com.opensymphony.xwork2.util.location.LocationAttributes.Pipe + * @since 2.1.8 + * @version $Id$ + */ +public class LocationAttributes { + /** Prefix for the location namespace */ + public static final String PREFIX = "loc"; + /** Namespace URI for location attributes */ + public static final String URI = "http://opensymphony.com/xwork/location"; + + /** Attribute name for the location URI */ + public static final String SRC_ATTR = "src"; + /** Attribute name for the line number */ + public static final String LINE_ATTR = "line"; + /** Attribute name for the column number */ + public static final String COL_ATTR = "column"; + + /** Attribute qualified name for the location URI */ + public static final String Q_SRC_ATTR = "loc:src"; + /** Attribute qualified name for the line number */ + public static final String Q_LINE_ATTR = "loc:line"; + /** Attribute qualified name for the column number */ + public static final String Q_COL_ATTR = "loc:column"; + + // Private constructor, we only have static methods + private LocationAttributes() { + // Nothing + } + + /** + * Add location attributes to a set of SAX attributes. + * + * @param locator the Locator (can be null) + * @param attrs the Attributes where locator information should be added + * @return Location enabled Attributes. + */ + public static Attributes addLocationAttributes(Locator locator, Attributes attrs) { + if (locator == null || attrs.getIndex(URI, SRC_ATTR) != -1) { + // No location information known, or already has it + return attrs; + } + + // Get an AttributeImpl so that we can add new attributes. + AttributesImpl newAttrs = attrs instanceof AttributesImpl ? + (AttributesImpl)attrs : new AttributesImpl(attrs); + + newAttrs.addAttribute(URI, SRC_ATTR, Q_SRC_ATTR, "CDATA", locator.getSystemId()); + newAttrs.addAttribute(URI, LINE_ATTR, Q_LINE_ATTR, "CDATA", Integer.toString(locator.getLineNumber())); + newAttrs.addAttribute(URI, COL_ATTR, Q_COL_ATTR, "CDATA", Integer.toString(locator.getColumnNumber())); + + return newAttrs; + } + + /** + * Returns the {@link Location} of an element (SAX flavor). + * + * @param attrs the element's attributes that hold the location information + * @param description a description for the location (can be null) + * @return a {@link Location} object + */ + public static Location getLocation(Attributes attrs, String description) { + String src = attrs.getValue(URI, SRC_ATTR); + if (src == null) { + return Location.UNKNOWN; + } + + return new LocationImpl(description, src, getLine(attrs), getColumn(attrs)); + } + + /** + * Returns the location of an element (SAX flavor). If the location is to be kept + * into an object built from this element, consider using {@link #getLocation(Attributes, String)} + * and the {@link Locatable} interface. + * + * @param attrs the element's attributes that hold the location information + * @return a location string as defined by {@link Location}. + */ + public static String getLocationString(Attributes attrs) { + String src = attrs.getValue(URI, SRC_ATTR); + if (src == null) { + return LocationUtils.UNKNOWN_STRING; + } + + return src + ":" + attrs.getValue(URI, LINE_ATTR) + ":" + attrs.getValue(URI, COL_ATTR); + } + + /** + * Returns the URI of an element (SAX flavor) + * + * @param attrs the element's attributes that hold the location information + * @return the element's URI or "[unknown location]" if attrs + * has no location information. + */ + public static String getURI(Attributes attrs) { + String src = attrs.getValue(URI, SRC_ATTR); + return src != null ? src : LocationUtils.UNKNOWN_STRING; + } + + /** + * Returns the line number of an element (SAX flavor) + * + * @param attrs the element's attributes that hold the location information + * @return the element's line number or -1 if attrs + * has no location information. + */ + public static int getLine(Attributes attrs) { + String line = attrs.getValue(URI, LINE_ATTR); + return line != null ? Integer.parseInt(line) : -1; + } + + /** + * Returns the column number of an element (SAX flavor) + * + * @param attrs the element's attributes that hold the location information + * @return the element's column number or -1 if attrs + * has no location information. + */ + public static int getColumn(Attributes attrs) { + String col = attrs.getValue(URI, COL_ATTR); + return col != null ? Integer.parseInt(col) : -1; + } + + /** + * Returns the {@link Location} of an element (DOM flavor). + * + * @param elem the element that holds the location information + * @param description a description for the location (if null, the element's name is used) + * @return a {@link Location} object + */ + public static Location getLocation(Element elem, String description) { + Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR); + if (srcAttr == null) { + return Location.UNKNOWN; + } + + return new LocationImpl(description == null ? elem.getNodeName() : description, + srcAttr.getValue(), getLine(elem), getColumn(elem)); + } + + /** + * Same as getLocation(elem, null). + */ + public static Location getLocation(Element elem) { + return getLocation(elem, null); + } + + + /** + * Returns the location of an element that has been processed by this pipe (DOM flavor). + * If the location is to be kept into an object built from this element, consider using + * {@link #getLocation(Element)} and the {@link Locatable} interface. + * + * @param elem the element that holds the location information + * @return a location string as defined by {@link Location}. + */ + public static String getLocationString(Element elem) { + Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR); + if (srcAttr == null) { + return LocationUtils.UNKNOWN_STRING; + } + + return srcAttr.getValue() + ":" + elem.getAttributeNS(URI, LINE_ATTR) + ":" + elem.getAttributeNS(URI, COL_ATTR); + } + + /** + * Returns the URI of an element (DOM flavor) + * + * @param elem the element that holds the location information + * @return the element's URI or "[unknown location]" if elem + * has no location information. + */ + public static String getURI(Element elem) { + Attr attr = elem.getAttributeNodeNS(URI, SRC_ATTR); + return attr != null ? attr.getValue() : LocationUtils.UNKNOWN_STRING; + } + + /** + * Returns the line number of an element (DOM flavor) + * + * @param elem the element that holds the location information + * @return the element's line number or -1 if elem + * has no location information. + */ + public static int getLine(Element elem) { + Attr attr = elem.getAttributeNodeNS(URI, LINE_ATTR); + return attr != null ? Integer.parseInt(attr.getValue()) : -1; + } + + /** + * Returns the column number of an element (DOM flavor) + * + * @param elem the element that holds the location information + * @return the element's column number or -1 if elem + * has no location information. + */ + public static int getColumn(Element elem) { + Attr attr = elem.getAttributeNodeNS(URI, COL_ATTR); + return attr != null ? Integer.parseInt(attr.getValue()) : -1; + } + + /** + * Remove the location attributes from a DOM element. + * + * @param elem the element to remove the location attributes from. + * @param recurse if true, also remove location attributes on descendant elements. + */ + public static void remove(Element elem, boolean recurse) { + elem.removeAttributeNS(URI, SRC_ATTR); + elem.removeAttributeNS(URI, LINE_ATTR); + elem.removeAttributeNS(URI, COL_ATTR); + if (recurse) { + NodeList children = elem.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + remove((Element)child, recurse); + } + } + } + } + + /** + * A SAX filter that adds the information available from the Locator as attributes. + * The purpose of having location as attributes is to allow this information to survive transformations + * of the document (an XSL could copy these attributes over) or conversion of SAX events to a DOM. + *

+ * The location is added as 3 attributes in a specific namespace to each element. + *

+     * <root xmlns:loc="http://opensymphony.com/xwork/location"
+     *       loc:src="file://path/to/file.xml"
+     *       loc:line="1" loc:column="1">
+     *   <foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/>
+     * </root>
+     * 
+ * Note: Although this adds a lot of information to the serialized form of the document, + * the overhead in SAX events is not that big, as attribute names are interned, and all src + * attributes point to the same string. + * + * @see com.opensymphony.xwork2.util.location.LocationAttributes + */ + public static class Pipe implements ContentHandler { + + private Locator locator; + + private ContentHandler nextHandler; + + /** + * Create a filter. It has to be chained to another handler to be really useful. + */ + public Pipe() { + } + + /** + * Create a filter that is chained to another handler. + * @param next the next handler in the chain. + */ + public Pipe(ContentHandler next) { + nextHandler = next; + } + + public void setDocumentLocator(Locator locator) { + this.locator = locator; + nextHandler.setDocumentLocator(locator); + } + + public void startDocument() throws SAXException { + nextHandler.startDocument(); + nextHandler.startPrefixMapping(LocationAttributes.PREFIX, LocationAttributes.URI); + } + + public void endDocument() throws SAXException { + endPrefixMapping(LocationAttributes.PREFIX); + nextHandler.endDocument(); + } + + public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { + // Add location attributes to the element + nextHandler.startElement(uri, loc, raw, LocationAttributes.addLocationAttributes(locator, attrs)); + } + + public void endElement(String arg0, String arg1, String arg2) throws SAXException { + nextHandler.endElement(arg0, arg1, arg2); + } + + public void startPrefixMapping(String arg0, String arg1) throws SAXException { + nextHandler.startPrefixMapping(arg0, arg1); + } + + public void endPrefixMapping(String arg0) throws SAXException { + nextHandler.endPrefixMapping(arg0); + } + + public void characters(char[] arg0, int arg1, int arg2) throws SAXException { + nextHandler.characters(arg0, arg1, arg2); + } + + public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { + nextHandler.ignorableWhitespace(arg0, arg1, arg2); + } + + public void processingInstruction(String arg0, String arg1) throws SAXException { + nextHandler.processingInstruction(arg0, arg1); + } + + public void skippedEntity(String arg0) throws SAXException { + nextHandler.skippedEntity(arg0); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java new file mode 100644 index 000000000..554098c60 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java @@ -0,0 +1,217 @@ +/* + * Copyright 2005 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.util.location; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * A simple immutable and serializable implementation of {@link Location}. + */ +public class LocationImpl implements Location, Serializable { + private final String uri; + private final int line; + private final int column; + private final String description; + + // Package private: outside this package, use Location.UNKNOWN. + static final LocationImpl UNKNOWN = new LocationImpl(null, null, -1, -1); + + /** + * Build a location for a given URI, with unknown line and column numbers. + * + * @param uri the resource URI + */ + public LocationImpl(String description, String uri) { + this(description, uri, -1, -1); + } + + /** + * Build a location for a given URI and line and column numbers. + * + * @param uri the resource URI + * @param line the line number (starts at 1) + * @param column the column number (starts at 1) + */ + public LocationImpl(String description, String uri, int line, int column) { + if (uri == null || uri.length() == 0) { + this.uri = null; + this.line = -1; + this.column = -1; + } else { + this.uri = uri; + this.line = line; + this.column = column; + } + + if (description != null && description.length() == 0) { + description = null; + } + this.description = description; + } + + /** + * Copy constructor. + * + * @param location the location to be copied + */ + public LocationImpl(Location location) { + this(location.getDescription(), location.getURI(), location.getLineNumber(), location.getColumnNumber()); + } + + /** + * Create a location from an existing one, but with a different description + */ + public LocationImpl(String description, Location location) { + this(description, location.getURI(), location.getLineNumber(), location.getColumnNumber()); + } + + /** + * Obtain a LocationImpl from a {@link Location}. If location is + * already a LocationImpl, it is returned, otherwise it is copied. + *

+ * This method is useful when an immutable and serializable location is needed, such as in locatable + * exceptions. + * + * @param location the location + * @return an immutable and serializable version of location + */ + public static LocationImpl get(Location location) { + if (location instanceof LocationImpl) { + return (LocationImpl)location; + } else if (location == null) { + return UNKNOWN; + } else { + return new LocationImpl(location); + } + } + + /** + * Get the description of this location + * + * @return the description (can be null) + */ + public String getDescription() { + return this.description; + } + + /** + * Get the URI of this location + * + * @return the URI (null if unknown). + */ + public String getURI() { + return this.uri; + } + + /** + * Get the line number of this location + * + * @return the line number (-1 if unknown) + */ + public int getLineNumber() { + return this.line; + } + + /** + * Get the column number of this location + * + * @return the column number (-1 if unknown) + */ + public int getColumnNumber() { + return this.column; + } + + /** + * Gets a source code snippet with the default padding + * + * @param padding The amount of lines before and after the error to include + */ + public List getSnippet(int padding) { + List snippet = new ArrayList(); + if (getLineNumber() > 0) { + try { + InputStream in = new URL(getURI()).openStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + int lineno = 0; + int errno = getLineNumber(); + String line; + while ((line = reader.readLine()) != null) { + lineno++; + if (lineno >= errno - padding && lineno <= errno + padding) { + snippet.add(line); + } + } + } catch (Exception ex) { + // ignoring as snippet not available isn't a big deal + } + } + return snippet; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Location) { + Location other = (Location)obj; + return this.line == other.getLineNumber() && this.column == other.getColumnNumber() + && testEquals(this.uri, other.getURI()) + && testEquals(this.description, other.getDescription()); + } + + return false; + } + + @Override + public int hashCode() { + int hash = line ^ column; + if (uri != null) hash ^= uri.hashCode(); + if (description != null) hash ^= description.hashCode(); + + return hash; + } + + @Override + public String toString() { + return LocationUtils.toString(this); + } + + /** + * Ensure serialized unknown location resolve to {@link Location#UNKNOWN}. + */ + private Object readResolve() { + return this.equals(Location.UNKNOWN) ? Location.UNKNOWN : this; + } + + private boolean testEquals(Object object1, Object object2) { + if (object1 == object2) { + return true; + } + if ((object1 == null) || (object2 == null)) { + return false; + } + return object1.equals(object2); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java new file mode 100644 index 000000000..0cbd53cb7 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java @@ -0,0 +1,305 @@ +/* + * Copyright 2005 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.util.location; + +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import org.w3c.dom.Element; +import org.xml.sax.Locator; +import org.xml.sax.SAXParseException; + +import javax.xml.transform.SourceLocator; +import javax.xml.transform.TransformerException; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Location-related utility methods. + */ +public class LocationUtils { + + /** + * The string representation of an unknown location: "[unknown location]". + */ + public static final String UNKNOWN_STRING = "[unknown location]"; + + private static List> finders = new ArrayList>(); + + /** + * An finder or object locations + */ + public interface LocationFinder { + /** + * Get the location of an object + * @param obj the object for which to find a location + * @param description and optional description to be added to the object's location + * @return the object's location or null if object's class isn't handled + * by this finder. + */ + Location getLocation(Object obj, String description); + } + + private LocationUtils() { + // Forbid instanciation + } + + /** + * Builds a string representation of a location, in the + * "descripton - uri:line:column" + * format (e.g. "foo - file://path/to/file.xml:3:40"). For {@link Location#UNKNOWN an unknown location}, returns + * {@link #UNKNOWN_STRING}. + * + * @return the string representation + */ + public static String toString(Location location) { + StringBuilder result = new StringBuilder(); + + String description = location.getDescription(); + if (description != null) { + result.append(description).append(" - "); + } + + String uri = location.getURI(); + if (uri != null) { + result.append(uri).append(':').append(location.getLineNumber()).append(':').append(location.getColumnNumber()); + } else { + result.append(UNKNOWN_STRING); + } + + return result.toString(); + } + + /** + * Parse a location string of the form "uri:line:column" (e.g. + * "path/to/file.xml:3:40") to a Location object. Additionally, a description may + * also optionally be present, separated with an hyphen (e.g. "foo - path/to/file.xml:3.40"). + * + * @param text the text to parse + * @return the location (possibly null if text was null or in an incorrect format) + */ + public static LocationImpl parse(String text) throws IllegalArgumentException { + if (text == null || text.length() == 0) { + return null; + } + + // Do we have a description? + String description; + int uriStart = text.lastIndexOf(" - "); // lastIndexOf to allow the separator to be in the description + if (uriStart > -1) { + description = text.substring(0, uriStart); + uriStart += 3; // strip " - " + } else { + description = null; + uriStart = 0; + } + + try { + int colSep = text.lastIndexOf(':'); + if (colSep > -1) { + int column = Integer.parseInt(text.substring(colSep + 1)); + + int lineSep = text.lastIndexOf(':', colSep - 1); + if (lineSep > -1) { + int line = Integer.parseInt(text.substring(lineSep + 1, colSep)); + return new LocationImpl(description, text.substring(uriStart, lineSep), line, column); + } + } else { + // unkonwn? + if (text.endsWith(UNKNOWN_STRING)) { + return LocationImpl.UNKNOWN; + } + } + } catch(Exception e) { + // Ignore: handled below + } + + return LocationImpl.UNKNOWN; + } + + /** + * Checks if a location is known, i.e. it is not null nor equal to {@link Location#UNKNOWN}. + * + * @param location the location to check + * @return true if the location is known + */ + public static boolean isKnown(Location location) { + return location != null && !Location.UNKNOWN.equals(location); + } + + /** + * Checks if a location is unknown, i.e. it is either null or equal to {@link Location#UNKNOWN}. + * + * @param location the location to check + * @return true if the location is unknown + */ + public static boolean isUnknown(Location location) { + return location == null || Location.UNKNOWN.equals(location); + } + + /** + * Add a {@link LocationFinder} to the list of finders that will be queried for an object's + * location by {@link #getLocation(Object, String)}. + *

+ * Important: LocationUtils internally stores a weak reference to the finder. This + * avoids creating strong links between the classloader holding this class and the finder's + * classloader, which can cause some weird memory leaks if the finder's classloader is to + * be reloaded. Therefore, you have to keep a strong reference to the finder in the + * calling code, e.g.: + *

+     *   private static LocationUtils.LocationFinder myFinder =
+     *       new LocationUtils.LocationFinder() {
+     *           public Location getLocation(Object obj, String desc) {
+     *               ...
+     *           }
+     *       };
+     *
+     *   static {
+     *       LocationUtils.addFinder(myFinder);
+     *   }
+     * 
+ * + * @param finder the location finder to add + */ + public static void addFinder(LocationFinder finder) { + if (finder == null) { + return; + } + + synchronized(LocationFinder.class) { + // Update a clone of the current finder list to avoid breaking + // any iteration occuring in another thread. + List> newFinders = new ArrayList>(finders); + newFinders.add(new WeakReference(finder)); + finders = newFinders; + } + } + + /** + * Get the location of an object. Some well-known located classes built in the JDK are handled + * by this method. Handling of other located classes can be handled by adding new location finders. + * + * @param obj the object of which to get the location + * @return the object's location, or {@link Location#UNKNOWN} if no location could be found + */ + public static Location getLocation(Object obj) { + return getLocation(obj, null); + } + + /** + * Get the location of an object. Some well-known located classes built in the JDK are handled + * by this method. Handling of other located classes can be handled by adding new location finders. + * + * @param obj the object of which to get the location + * @param description an optional description of the object's location, used if a Location object + * has to be created. + * @return the object's location, or {@link Location#UNKNOWN} if no location could be found + */ + public static Location getLocation(Object obj, String description) { + if (obj instanceof Location) { + return (Location) obj; + } + + if (obj instanceof Locatable) { + return ((Locatable)obj).getLocation(); + } + + // Check some well-known locatable exceptions + if (obj instanceof SAXParseException) { + SAXParseException spe = (SAXParseException)obj; + if (spe.getSystemId() != null) { + return new LocationImpl(description, spe.getSystemId(), spe.getLineNumber(), spe.getColumnNumber()); + } else { + return Location.UNKNOWN; + } + } + + if (obj instanceof TransformerException) { + TransformerException ex = (TransformerException)obj; + SourceLocator locator = ex.getLocator(); + if (locator != null && locator.getSystemId() != null) { + return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber()); + } else { + return Location.UNKNOWN; + } + } + + if (obj instanceof Locator) { + Locator locator = (Locator)obj; + if (locator.getSystemId() != null) { + return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber()); + } else { + return Location.UNKNOWN; + } + } + + if (obj instanceof Element) { + return LocationAttributes.getLocation((Element)obj); + } + + List> currentFinders = finders; // Keep the current list + int size = currentFinders.size(); + for (int i = 0; i < size; i++) { + WeakReference ref = currentFinders.get(i); + LocationFinder finder = ref.get(); + if (finder == null) { + // This finder was garbage collected: update finders + synchronized(LocationFinder.class) { + // Update a clone of the current list to avoid breaking current iterations + List> newFinders = new ArrayList>(finders); + newFinders.remove(ref); + finders = newFinders; + } + } + + Location result = finder.getLocation(obj, description); + if (result != null) { + return result; + } + } + + if (obj instanceof Throwable) { + Throwable t = (Throwable) obj; + StackTraceElement[] stack = t.getStackTrace(); + if (stack != null && stack.length > 0) { + StackTraceElement trace = stack[0]; + if (trace.getLineNumber() >= 0) { + String uri = trace.getClassName(); + if (trace.getFileName() != null) { + uri = uri.replace('.','/'); + uri = uri.substring(0, uri.lastIndexOf('/') + 1); + uri = uri + trace.getFileName(); + URL url = ClassLoaderUtil.getResource(uri, LocationUtils.class); + if (url != null) { + uri = url.toString(); + } + } + if (description == null) { + StringBuilder sb = new StringBuilder(); + sb.append("Class: ").append(trace.getClassName()).append("\n"); + sb.append("File: ").append(trace.getFileName()).append("\n"); + sb.append("Method: ").append(trace.getMethodName()).append("\n"); + sb.append("Line: ").append(trace.getLineNumber()); + description = sb.toString(); + } + return new LocationImpl(description, uri, trace.getLineNumber(), -1); + } + } + } + + return Location.UNKNOWN; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/package.html new file mode 100644 index 000000000..840814bd9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/location/package.html @@ -0,0 +1,3 @@ + + Classes and utilities used to track location information. + \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java new file mode 100644 index 000000000..d9fc1fb46 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java @@ -0,0 +1,45 @@ +/* + * 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.util.logging; + +/** + * Main logger interface for logging things + */ +public interface Logger { + void trace(String msg, String... args); + void trace(String msg, Throwable ex, String... args); + boolean isTraceEnabled(); + + void debug(String msg, String... args); + void debug(String msg, Throwable ex, String... args); + boolean isDebugEnabled(); + + void info(String msg, String... args); + void info(String msg, Throwable ex, String... args); + boolean isInfoEnabled(); + + void warn(String msg, String... args); + void warn(String msg, Throwable ex, String... args); + boolean isWarnEnabled(); + + void error(String msg, String... args); + void error(String msg, Throwable ex, String... args); + boolean isErrorEnabled(); + + void fatal(String msg, String... args); + void fatal(String msg, Throwable ex, String... args); + boolean isFatalEnabled(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java new file mode 100644 index 000000000..5dac28f53 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java @@ -0,0 +1,80 @@ +/* + * 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.util.logging; + +import com.opensymphony.xwork2.util.logging.jdk.JdkLoggerFactory; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Creates loggers. Static accessor will lazily try to decide on the best factory if none specified. + */ +public abstract class LoggerFactory { + + private static final ReadWriteLock lock = new ReentrantReadWriteLock(); + private static LoggerFactory factory; + + public static void setLoggerFactory(LoggerFactory factory) { + lock.writeLock().lock(); + try { + LoggerFactory.factory = factory; + } finally { + lock.writeLock().unlock(); + } + + } + + public static Logger getLogger(Class cls) { + return getLoggerFactory().getLoggerImpl(cls); + } + + public static Logger getLogger(String name) { + return getLoggerFactory().getLoggerImpl(name); + } + + protected static LoggerFactory getLoggerFactory() { + lock.readLock().lock(); + try { + if (factory != null) { + return factory; + } + } finally { + lock.readLock().unlock(); + } + lock.writeLock().lock(); + try { + if (factory == null) { + try { + Class.forName("org.apache.commons.logging.LogFactory"); + factory = new com.opensymphony.xwork2.util.logging.commons.CommonsLoggerFactory(); + } catch (ClassNotFoundException ex) { + // commons logging not found, falling back to jdk logging + factory = new JdkLoggerFactory(); + } + } + return factory; + } + finally { + lock.writeLock().unlock(); + } + } + + protected abstract Logger getLoggerImpl(Class cls); + + protected abstract Logger getLoggerImpl(String name); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java new file mode 100644 index 000000000..da565504d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java @@ -0,0 +1,72 @@ +/* + * 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.util.logging; + +/** + * Logging utility methods + */ +public class LoggerUtils { + + /** + * Formats messages using parameters. For example, the call: + * + *
+     * format("foo #1", "bob");
+     * 
+ * + * will return: + *
+     * foo bob
+     * 
+ * + * @param msg The message + * @param args A list of arguments. A maximum of 10 are supported. + * @return The formatted string + */ + public static String format(String msg, String... args) { + if (msg != null && msg.length() > 0 && msg.indexOf('#') > -1) { + StringBuilder sb = new StringBuilder(); + boolean isArg = false; + for (int x = 0; x < msg.length(); x++) { + char c = msg.charAt(x); + if (isArg) { + isArg = false; + if (Character.isDigit(c)) { + int val = Character.getNumericValue(c); + if (val >= 0 && val < args.length) { + sb.append(args[val]); + continue; + } + } + sb.append('#'); + } + if (c == '#') { + isArg = true; + continue; + } + sb.append(c); + } + + if (isArg) { + sb.append('#'); + } + return sb.toString(); + } + return msg; + + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java new file mode 100644 index 000000000..9e88930fa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java @@ -0,0 +1,108 @@ +/* + * 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.util.logging.commons; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerUtils; +import org.apache.commons.logging.Log; + +/** + * Simple logger that delegates to commons logging + */ +public class CommonsLogger implements Logger { + + private Log log; + + public CommonsLogger(Log log) { + this.log = log; + } + + public void error(String msg, String... args) { + log.error(LoggerUtils.format(msg, args)); + } + + public void error(String msg, Throwable ex, String... args) { + log.error(LoggerUtils.format(msg, args), ex); + } + + public void info(String msg, String... args) { + log.info(LoggerUtils.format(msg, args)); + } + + public void info(String msg, Throwable ex, String... args) { + log.info(LoggerUtils.format(msg, args), ex); + } + + + + public boolean isInfoEnabled() { + return log.isInfoEnabled(); + } + + public void warn(String msg, String... args) { + log.warn(LoggerUtils.format(msg, args)); + } + + public void warn(String msg, Throwable ex, String... args) { + log.warn(LoggerUtils.format(msg, args), ex); + } + + public boolean isDebugEnabled() { + return log.isDebugEnabled(); + } + + public void debug(String msg, String... args) { + log.debug(LoggerUtils.format(msg, args)); + } + + public void debug(String msg, Throwable ex, String... args) { + log.debug(LoggerUtils.format(msg, args), ex); + } + + public boolean isTraceEnabled() { + return log.isTraceEnabled(); + } + + public void trace(String msg, String... args) { + log.trace(LoggerUtils.format(msg, args)); + } + + public void trace(String msg, Throwable ex, String... args) { + log.trace(LoggerUtils.format(msg, args), ex); + } + + + public void fatal(String msg, String... args) { + log.fatal(LoggerUtils.format(msg, args)); + } + + public void fatal(String msg, Throwable ex, String... args) { + log.fatal(LoggerUtils.format(msg, args), ex); + } + + public boolean isErrorEnabled() { + return log.isErrorEnabled(); + } + + public boolean isFatalEnabled() { + return log.isFatalEnabled(); + } + + public boolean isWarnEnabled() { + return log.isWarnEnabled(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java new file mode 100644 index 000000000..3979e9c00 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.logging.commons; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.commons.logging.LogFactory; + +/** + * Creates commons-logging-backed loggers + */ +public class CommonsLoggerFactory extends LoggerFactory { + + @Override + protected Logger getLoggerImpl(Class cls) { + return new CommonsLogger(LogFactory.getLog(cls)); + } + + @Override + protected Logger getLoggerImpl(String name) { + return new CommonsLogger(LogFactory.getLog(name)); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java new file mode 100644 index 000000000..2aaec2470 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java @@ -0,0 +1,106 @@ +/* + * 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.util.logging.jdk; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerUtils; + +import java.util.logging.Level; + +/** + * Delegates to jdk logger. Maps fatal to Level.SEVERE along with error. + */ +public class JdkLogger implements Logger { + + private java.util.logging.Logger log; + + public JdkLogger(java.util.logging.Logger log) { + this.log = log; + } + + public void error(String msg, String... args) { + log.log(Level.SEVERE, LoggerUtils.format(msg, args)); + } + + public void error(String msg, Throwable ex, String... args) { + log.log(Level.SEVERE, LoggerUtils.format(msg, args), ex); + } + + public void fatal(String msg, String... args) { + log.log(Level.SEVERE, LoggerUtils.format(msg, args)); + } + + public void fatal(String msg, Throwable ex, String... args) { + log.log(Level.SEVERE, LoggerUtils.format(msg, args), ex); + } + + public void info(String msg, String... args) { + log.log(Level.INFO, LoggerUtils.format(msg, args)); + } + + public void info(String msg, Throwable ex, String... args) { + log.log(Level.INFO, LoggerUtils.format(msg, args), ex); + } + + public boolean isInfoEnabled() { + return log.isLoggable(Level.INFO); + } + + public void warn(String msg, String... args) { + log.log(Level.WARNING, LoggerUtils.format(msg, args)); + } + + public void warn(String msg, Throwable ex, String... args) { + log.log(Level.WARNING, LoggerUtils.format(msg, args), ex); + } + + public boolean isDebugEnabled() { + return log.isLoggable(Level.FINE); + } + + public void debug(String msg, String... args) { + log.log(Level.FINE, LoggerUtils.format(msg, args)); + } + + public void debug(String msg, Throwable ex, String... args) { + log.log(Level.FINE, LoggerUtils.format(msg, args), ex); + } + + public boolean isTraceEnabled() { + return log.isLoggable(Level.FINEST); + } + + public void trace(String msg, String... args) { + log.log(Level.FINEST, LoggerUtils.format(msg, args)); + } + + public void trace(String msg, Throwable ex, String... args) { + log.log(Level.FINEST, LoggerUtils.format(msg, args), ex); + } + + public boolean isErrorEnabled() { + return log.isLoggable(Level.SEVERE); + } + + public boolean isFatalEnabled() { + return log.isLoggable(Level.SEVERE); + } + + public boolean isWarnEnabled() { + return log.isLoggable(Level.WARNING); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java new file mode 100644 index 000000000..af8b67747 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java @@ -0,0 +1,35 @@ +/* + * 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.util.logging.jdk; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + * Creates jdk loggers + */ +public class JdkLoggerFactory extends LoggerFactory { + + @Override + protected Logger getLoggerImpl(Class cls) { + return new JdkLogger(java.util.logging.Logger.getLogger(cls.getName())); + } + + @Override + protected Logger getLoggerImpl(String name) { + return new JdkLogger(java.util.logging.Logger.getLogger(name)); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/util/package.html new file mode 100644 index 000000000..e400bf97f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/package.html @@ -0,0 +1 @@ +XWork util classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java new file mode 100644 index 000000000..d374a2c0c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Atlassian Software Systems Pty Ltd nor the names of + * its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.opensymphony.xwork2.util.profiling; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * @author Scott Farquhar + */ +public class ObjectProfiler +{ + + /** + * Given a class, and an interface that it implements, return a proxied version of the class that implements + * the interface. + *

+ * The usual use of this is to profile methods from Factory objects: + *

+     * public PersistenceManager getPersistenceManager()
+     * {
+     *   return new DefaultPersistenceManager();
+     * }
+     *
+     * instead write:
+     * public PersistenceManager getPersistenceManager()
+     * {
+     *   return ObjectProfiler.getProfiledObject(PersistenceManager.class, new DefaultPersistenceManager());
+     * }
+     * 
+ *

+ * A side effect of this is that you will no longer be able to downcast to DefaultPersistenceManager. This is probably a *good* thing. + * + * @param interfaceClazz The interface to implement. + * @param o The object to proxy + * @return A proxied object, or the input object if the interfaceClazz wasn't an interface. + */ + public static Object getProfiledObject(Class interfaceClazz, Object o) + { + //if we are not active - then do nothing + if (!UtilTimerStack.isActive()) + return o; + + //this should always be true - you shouldn't be passing something that isn't an interface + if (interfaceClazz.isInterface()) + { + InvocationHandler timerHandler = new TimerInvocationHandler(o); + return Proxy.newProxyInstance(interfaceClazz.getClassLoader(), + new Class[]{interfaceClazz}, timerHandler); + } + else + { + return o; + } + } + + /** + * A profiled call {@link Method#invoke(java.lang.Object, java.lang.Object[])}. If {@link UtilTimerStack#isActive() } + * returns false, then no profiling is performed. + */ + public static Object profiledInvoke(Method target, Object value, Object[] args) throws IllegalAccessException, InvocationTargetException + { + //if we are not active - then do nothing + if (!UtilTimerStack.isActive()) + return target.invoke(value, args); + + String logLine = new String(getTrimmedClassName(target) + "." + target.getName() + "()"); + + UtilTimerStack.push(logLine); + try + { + Object returnValue = target.invoke(value, args); + + //if the return value is an interface then we should also proxy it! + if (returnValue != null && target.getReturnType().isInterface()) + { +// System.out.println("Return type " + returnValue.getClass().getName() + " is being proxied " + target.getReturnType().getName() + " " + logLine); + InvocationHandler timerHandler = new TimerInvocationHandler(returnValue); + return Proxy.newProxyInstance(returnValue.getClass().getClassLoader(), + new Class[]{target.getReturnType()}, timerHandler); + } + else + { + return returnValue; + } + } + finally + { + UtilTimerStack.pop(logLine); + } + } + + /** + * Given a method, get the Method name, with no package information. + */ + public static String getTrimmedClassName(Method method) + { + String classname = method.getDeclaringClass().getName(); + return classname.substring(classname.lastIndexOf('.') + 1); + } + +} + +class TimerInvocationHandler implements InvocationHandler +{ + protected Object target; + + public TimerInvocationHandler(Object target) + { + if (target == null) + throw new IllegalArgumentException("Target Object passed to timer cannot be null"); + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + return ObjectProfiler.profiledInvoke(method, target, args); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java new file mode 100644 index 000000000..35baaa4d4 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Atlassian Software Systems Pty Ltd nor the names of + * its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.opensymphony.xwork2.util.profiling; + +import java.util.ArrayList; +import java.util.List; + +/** + * Bean to contain information about the pages profiled + * + * @author Mike Cannon-Brookes + * @author Scott Farquhar + * + * @version $Date$ $Id$ + */ +public class ProfilingTimerBean implements java.io.Serializable { + + private static final long serialVersionUID = -6180672043920208784L; + + List children = new ArrayList(); + ProfilingTimerBean parent = null; + + String resource; + + long startTime; + long totalTime; + + public ProfilingTimerBean(String resource) + { + this.resource = resource; + } + + protected void addParent(ProfilingTimerBean parent) + { + this.parent = parent; + } + + public ProfilingTimerBean getParent() + { + return parent; + } + + + public void addChild(ProfilingTimerBean child) + { + children.add(child); + child.addParent(this); + } + + + public void setStartTime() + { + this.startTime = System.currentTimeMillis(); + } + + public void setEndTime() + { + this.totalTime = System.currentTimeMillis() - startTime; + } + + public String getResource() + { + return resource; + } + + /** + * Get a formatted string representing all the methods that took longer than a specified time. + */ + + public String getPrintable(long minTime) + { + return getPrintable("", minTime); + } + + protected String getPrintable(String indent, long minTime) + { + //only print the value if we are larger or equal to the min time. + if (totalTime >= minTime) + { + StringBuilder buffer = new StringBuilder(); + buffer.append(indent); + buffer.append("[" + totalTime + "ms] - " + resource); + buffer.append("\n"); + + for (ProfilingTimerBean aChildren : children) { + buffer.append((aChildren).getPrintable(indent + " ", minTime)); + } + + return buffer.toString(); + } + else + return ""; + } +} + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java new file mode 100644 index 000000000..2322f939c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Atlassian Software Systems Pty Ltd nor the names of + * its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.opensymphony.xwork2.util.profiling; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + + +/** + * A timer stack. + * + *

+ * + * + * + * Struts2 profiling aspects involves the following :- + *

    + *
  • ActionContextCleanUp
  • + *
  • FreemarkerPageFilter
  • + *
  • DispatcherFilter
  • + *
      + *
    • Dispatcher
    • + *
        + *
      • creation of DefaultActionProxy
      • + *
          + *
        • creation of DefaultActionInvocation
        • + *
            + *
          • creation of Action
          • + *
          + *
        + *
      • execution of DefaultActionProxy
      • + *
          + *
        • invocation of DefaultActionInvocation
        • + *
            + *
          • invocation of Interceptors
          • + *
          • invocation of Action
          • + *
          • invocation of PreResultListener
          • + *
          • invocation of Result
          • + *
          + *
        + *
      + *
    + *
+ * + * + * + * + * + * + * XWork2 profiling aspects involves the following :- + *
    + *
      + *
    • creation of DefaultActionProxy
    • + *
        + *
      • creation of DefaultActionInvocation
      • + *
          + *
        • creation of Action
        • + *
        + *
      + *
    • execution of DefaultActionProxy
    • + *
        + *
      • invocation of DefaultActionInvocation
      • + *
          + *
        • invocation of Interceptors
        • + *
        • invocation of Action
        • + *
        • invocation of PreResultListener
        • + *
        • invocation of Result
        • + *
        + *
      + *
    + *
+ * + * + * + * + * + * + * Activating / Deactivating of the profiling feature could be done through:- + * + * + * + *

+ * + * System properties:-

+ *

+ * 
+ * 
+ *  -Dxwork.profile.activate=true
+ *  
+ *  
+ * 
+ * + * + * + * This could be done in the container startup script eg. CATALINA_OPTS in catalina.sh + * (tomcat) or using "java -Dxwork.profile.activate=true -jar start.jar" (jetty) + * + * + * + *

+ * Code :-

+ *

+ * 
+ *   
+ *  UtilTimerStack.setActivate(true);
+ *    
+ *  
+ * 
+ * + * + * + * + * + * This could be done in a static block, in a Spring bean with lazy-init="false", + * in a Servlet with init-on-startup as some numeric value, in a Filter or + * Listener's init method etc. + * + * + * + *

+ * Parameter:- + * + *

+ * 
+ * 
+ * <action ... >  
+ *  ...
+ *  <interceptor-ref name="profiling">
+ *      <param name="profilingKey">profiling</param>
+ *  </interceptor-ref>
+ *  ...
+ * </action>
+ * 
+ * or 
+ * 
+ * <action .... >
+ * ...
+ *  <interceptor-ref name="profiling" />
+ * ...
+ * </action>
+ * 
+ * through url
+ * 
+ * http://host:port/context/namespace/someAction.action?profiling=true
+ * 
+ * through code
+ * 
+ * ActionContext.getContext().getParameters().put("profiling", "true);
+ * 
+ * 
+ * 
+ * + * + * + * + * To use profiling activation through parameter, one will need to pass in through + * the 'profiling' parameter (which is the default) and could be changed through + * the param tag in the interceptor-ref. + * + * + * + *

+ * Warning:

+ * + * + * Profiling activation through a parameter requires the following: + * + *

    + *
  • Profiling interceptor in interceptor stack
  • + *
  • dev mode on (struts.devMode=true in struts.properties) + *
+ * + * + * + *

+ * + * + * + * One could filter out the profile logging by having a System property as follows. With this + * 'xwork.profile.mintime' property, one could only log profile information when its execution time + * exceed those specified in 'xwork.profile.mintime' system property. If no such property is specified, + * it will be assumed to be 0, hence all profile information will be logged. + * + * + * + *

+ * 
+ * 
+ *  -Dxwork.profile.mintime=10000
+ * 
+ * 
+ * 
+ * + * + * + * One could extend the profiling feature provided by Struts2 in their web application as well. + * + * + * + *
+ * 
+ * 
+ *    String logMessage = "Log message";
+ *    UtilTimerStack.push(logMessage);
+ *    try {
+ *        // do some code
+ *    }
+ *    finally {
+ *        UtilTimerStack.pop(logMessage); // this needs to be the same text as above
+ *    }
+ *    
+ *    
+ * 
+ * + * or + * + *
+ * 
+ * 
+ *   String result = UtilTimerStack.profile("purchaseItem: ", 
+ *       new UtilTimerStack.ProfilingBlock() {
+ *            public String doProfiling() {
+ *               // do some code
+ *               return "Ok";
+ *            }
+ *       });
+ *       
+ *       
+ * 
+ * + * + * + * + * Profiled result is logged using commons-logging under the logger named + * 'com.opensymphony.xwork2.util.profiling.UtilTimerStack'. Depending on the underlying logging implementation + * say if it is Log4j, one could direct the log to appear in a different file, being emailed to someone or have + * it stored in the db. + * + * + * + * @version $Date$ $Id$ + */ +public class UtilTimerStack +{ + + // A reference to the current ProfilingTimerBean + protected static ThreadLocal current = new ThreadLocal(); + + /** + * System property that controls whether this timer should be used or not. Set to "true" activates + * the timer. Set to "false" to disactivate. + */ + public static final String ACTIVATE_PROPERTY = "xwork.profile.activate"; + + /** + * System property that controls the min time, that if exceeded will cause a log (at INFO level) to be + * created. + */ + public static final String MIN_TIME = "xwork.profile.mintime"; + + private static final Logger LOG = LoggerFactory.getLogger(UtilTimerStack.class); + + /** + * Initialized in a static block, it can be changed at runtime by calling setActive(...) + */ + private static boolean active; + + static { + active = "true".equalsIgnoreCase(System.getProperty(ACTIVATE_PROPERTY)); + } + + /** + * Create and start a performance profiling with the name given. Deal with + * profile hierarchy automatically, so caller don't have to be concern about it. + * + * @param name profile name + */ + public static void push(String name) + { + if (!isActive()) + return; + + //create a new timer and start it + ProfilingTimerBean newTimer = new ProfilingTimerBean(name); + newTimer.setStartTime(); + + //if there is a current timer - add the new timer as a child of it + ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get(); + if (currentTimer != null) + { + currentTimer.addChild(newTimer); + } + + //set the new timer to be the current timer + current.set(newTimer); + } + + /** + * End a preformance profiling with the name given. Deal with + * profile hierarchy automatically, so caller don't have to be concern about it. + * + * @param name profile name + */ + public static void pop(String name) + { + if (!isActive()) + return; + + ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get(); + + //if the timers are matched up with each other (ie push("a"); pop("a")); + if (currentTimer != null && name != null && name.equals(currentTimer.getResource())) + { + currentTimer.setEndTime(); + ProfilingTimerBean parent = currentTimer.getParent(); + //if we are the root timer, then print out the times + if (parent == null) + { + printTimes(currentTimer); + current.set(null); //for those servers that use thread pooling + } + else + { + current.set(parent); + } + } + else + { + //if timers are not matched up, then print what we have, and then print warning. + if (currentTimer != null) + { + printTimes(currentTimer); + current.set(null); //prevent printing multiple times + LOG.warn("Unmatched Timer. Was expecting " + currentTimer.getResource() + ", instead got " + name); + } + } + + + } + + /** + * Do a log (at INFO level) of the time taken for this particular profiling. + * + * @param currentTimer profiling timer bean + */ + private static void printTimes(ProfilingTimerBean currentTimer) + { + LOG.info(currentTimer.getPrintable(getMinTime())); + } + + /** + * Get the min time for this profiling, it searches for a System property + * 'xwork.profile.mintime' and default to 0. + * + * @return long + */ + private static long getMinTime() + { + try + { + return Long.parseLong(System.getProperty(MIN_TIME, "0")); + } + catch (NumberFormatException e) + { + return -1; + } + } + + /** + * Determine if profiling is being activated, by searching for a system property + * 'xwork.profile.activate', default to false (profiling is off). + * + * @return true, if active, false otherwise. + */ + public static boolean isActive() + { + return active; + } + + /** + * Turn profiling on or off. + * + * @param active + */ + public static void setActive(boolean active) + { + if (active) + System.setProperty(ACTIVATE_PROPERTY, "true"); + else + System.clearProperty(ACTIVATE_PROPERTY); + + UtilTimerStack.active = active; + } + + + /** + * A convenience method that allows block of code subjected to profiling to be executed + * and avoid the need of coding boiler code that does pushing (UtilTimeBean.push(...)) and + * poping (UtilTimerBean.pop(...)) in a try ... finally ... block. + * + *

+ * + * Example of usage: + *

+     * 	 // we need a returning result
+     *   String result = UtilTimerStack.profile("purchaseItem: ", 
+     *       new UtilTimerStack.ProfilingBlock() {
+     *            public String doProfiling() {
+     *               getMyService().purchaseItem(....)
+     *               return "Ok";
+     *            }
+     *       });
+     * 
+ * or + *
+     *   // we don't need a returning result
+     *   UtilTimerStack.profile("purchaseItem: ", 
+     *       new UtilTimerStack.ProfilingBlock() {
+     *            public String doProfiling() {
+     *               getMyService().purchaseItem(....)
+     *               return null;
+     *            }
+     *       });
+     * 
+ * + * @param any return value if there's one. + * @param name profile name + * @param block code block subjected to profiling + * @return T + * @throws Exception + */ + public static T profile(String name, ProfilingBlock block) throws Exception { + UtilTimerStack.push(name); + try { + return block.doProfiling(); + } + finally { + UtilTimerStack.pop(name); + } + } + + /** + * A callback interface where code subjected to profile is to be executed. This eliminates the need + * of coding boiler code that does pushing (UtilTimerBean.push(...)) and poping (UtilTimerBean.pop(...)) + * in a try ... finally ... block. + * + * @version $Date$ $Id$ + * + * @param + */ + public static interface ProfilingBlock { + + /** + * Method that execute the code subjected to profiling. + * + * @return profiles Type + * @throws Exception + */ + T doProfiling() throws Exception; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html new file mode 100644 index 000000000..c0b1f7f63 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html @@ -0,0 +1 @@ +Classes to enable profiling of action execution. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java new file mode 100644 index 000000000..704b5af71 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java @@ -0,0 +1,15 @@ +package com.opensymphony.xwork2.util.reflection; + +import java.util.Map; + +public interface ReflectionContextFactory { + /** + * Creates and returns a new standard naming context for evaluating an OGNL + * expression. + * + * @param root the root of the object graph + * @return a new Map with the keys root and context + * set appropriately + */ + Map createDefaultContext( Object root ); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java new file mode 100644 index 000000000..60fd456f9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java @@ -0,0 +1,179 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.util.reflection; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; + +import java.util.HashMap; +import java.util.Map; + +/** + * Manages variables in the reflection context and returns values + * to be used by the application. + * + * @author Gabe + */ +public class ReflectionContextState { + + public static final String CURRENT_PROPERTY_PATH="current.property.path"; + public static final String FULL_PROPERTY_PATH="current.property.path"; + private static final String GETTING_BY_KEY_PROPERTY="xwork.getting.by.key.property"; + + private static final String SET_MAP_KEY="set.map.key"; + + public static boolean isCreatingNullObjects(Map context) { + //TODO + return getBooleanProperty(ReflectionContextState.CREATE_NULL_OBJECTS, context); + } + + public static void setCreatingNullObjects(Map context, boolean creatingNullObjects) { + setBooleanValue(ReflectionContextState.CREATE_NULL_OBJECTS, context, creatingNullObjects); + } + + public static boolean isGettingByKeyProperty(Map context) { + return getBooleanProperty(GETTING_BY_KEY_PROPERTY, context); + } + + public static void setDenyMethodExecution(Map context, boolean denyMethodExecution) { + setBooleanValue(ReflectionContextState.DENY_METHOD_EXECUTION, context, denyMethodExecution); + } + + public static boolean isDenyMethodExecution(Map context) { + return getBooleanProperty(ReflectionContextState.DENY_METHOD_EXECUTION, context); + } + + public static void setGettingByKeyProperty(Map context, boolean gettingByKeyProperty) { + setBooleanValue(GETTING_BY_KEY_PROPERTY, context, gettingByKeyProperty); + } + + public static boolean isReportingConversionErrors(Map context) { + return getBooleanProperty(XWorkConverter.REPORT_CONVERSION_ERRORS, context); + } + + public static void setReportingConversionErrors(Map context, boolean reportingErrors) { + setBooleanValue(XWorkConverter.REPORT_CONVERSION_ERRORS, context, reportingErrors); + } + + public static Class getLastBeanClassAccessed(Map context) { + return (Class)context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + } + + public static void setLastBeanPropertyAccessed(Map context, String property) { + context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, property); + } + + public static String getLastBeanPropertyAccessed(Map context) { + return (String)context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + } + + public static void setLastBeanClassAccessed(Map context, Class clazz) { + context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, clazz); + } + /** + * Gets the current property path but not completely. + * It does not use the [ and ] used in some representations + * of Maps and Lists. The reason for this is that the current + * property path is only currently used for caching purposes + * so there is no real reason to have an exact replica. + * + *

So if the real path is myProp.myMap['myKey'] this would + * return myProp.myMap.myKey. + * + * @param context + */ + public static String getCurrentPropertyPath(Map context) { + return (String)context.get(CURRENT_PROPERTY_PATH); + } + + public static String getFullPropertyPath(Map context) { + return (String)context.get(FULL_PROPERTY_PATH); + } + + public static void setFullPropertyPath(Map context, String path) { + context.put(FULL_PROPERTY_PATH, path); + + } + + public static void updateCurrentPropertyPath(Map context, Object name) { + String currentPath=getCurrentPropertyPath(context); + if (name!=null) { + if (currentPath!=null) { + StringBuilder sb = new StringBuilder(currentPath); + sb.append("."); + sb.append(name.toString()); + currentPath = sb.toString(); + } else { + currentPath = name.toString(); + } + context.put(CURRENT_PROPERTY_PATH, currentPath); + } + } + + public static void setSetMap(Map context, Map setMap, String path) { + Map> mapOfSetMaps=(Map)context.get(SET_MAP_KEY); + if (mapOfSetMaps==null) { + mapOfSetMaps=new HashMap>(); + context.put(SET_MAP_KEY, mapOfSetMaps); + } + mapOfSetMaps.put(path, setMap); + } + + public static Map getSetMap(Map context, String path) { + Map> mapOfSetMaps=(Map)context.get(SET_MAP_KEY); + if (mapOfSetMaps==null) { + return null; + } + return mapOfSetMaps.get(path); + } + + private static boolean getBooleanProperty(String property, Map context) { + Boolean myBool=(Boolean)context.get(property); + return (myBool==null)?false:myBool.booleanValue(); + } + + private static void setBooleanValue(String property, Map context, boolean value) { + context.put(property, new Boolean(value)); + } + + /** + * + */ + public static void clearCurrentPropertyPath(Map context) { + context.put(CURRENT_PROPERTY_PATH, null); + + } + + + public static void clear(Map context) { + if (context != null) { + context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED,null); + context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED,null); + + context.put(CURRENT_PROPERTY_PATH,null); + context.put(FULL_PROPERTY_PATH,null); + } + + } + + + public static final String CREATE_NULL_OBJECTS = "xwork.NullHandler.createNullObjects"; + public static final String DENY_METHOD_EXECUTION = "xwork.MethodAccessor.denyMethodExecution"; + public static final String DENY_INDEXED_ACCESS_EXECUTION = "xwork.IndexedPropertyAccessor.denyMethodExecution"; + + + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java new file mode 100644 index 000000000..a6d107d9b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java @@ -0,0 +1,41 @@ +package com.opensymphony.xwork2.util.reflection; + +import com.opensymphony.xwork2.XWorkException; + +public class ReflectionException extends XWorkException { + + public ReflectionException() { + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s) { + super(s); + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s, Object target) { + super(s, target); + // TODO Auto-generated constructor stub + } + + public ReflectionException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + public ReflectionException(Throwable cause, Object target) { + super(cause, target); + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s, Throwable cause) { + super(s, cause); + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s, Throwable cause, Object target) { + super(s, cause, target); + // TODO Auto-generated constructor stub + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java new file mode 100644 index 000000000..b5c7d8a5d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java @@ -0,0 +1,14 @@ +package com.opensymphony.xwork2.util.reflection; + +/** + * Declares a class that wants to handle its own reflection exceptions + */ +public interface ReflectionExceptionHandler { + + /** + * Handles a reflection exception + * + * @param ex The reflection exception + */ + void handle(ReflectionException ex); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java new file mode 100644 index 000000000..0230c60d3 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java @@ -0,0 +1,141 @@ +package com.opensymphony.xwork2.util.reflection; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +public interface ReflectionProvider { + + Method getGetMethod(Class targetClass, String propertyName) throws IntrospectionException, ReflectionException; + + Method getSetMethod(Class targetClass, String propertyName) throws IntrospectionException, ReflectionException; + + Field getField(Class inClass, String name); + + /** + * Sets the object's properties using the default type converter, defaulting to not throw + * exceptions for problems setting the properties. + * + * @param props the properties being set + * @param o the object + * @param context the action context + */ + void setProperties(Map props, Object o, Map context); + + /** + * Sets the object's properties using the default type converter. + * + * @param props the properties being set + * @param o the object + * @param context the action context + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the properties + */ + void setProperties(Map props, Object o, Map context, boolean throwPropertyExceptions) throws ReflectionException; + + /** + * Sets the properties on the object using the default context, defaulting to not throwing + * exceptions for problems setting the properties. + * + * @param properties + * @param o + */ + void setProperties(Map properties, Object o); + + /** + * This method returns a PropertyDescriptor for the given class and property name using + * a Map lookup (using getPropertyDescriptorsMap()). + */ + PropertyDescriptor getPropertyDescriptor(Class targetClass, String propertyName) throws IntrospectionException, ReflectionException; + + /** + * Copies the properties in the object "from" and sets them in the object "to" + * using specified type converter, or {@link com.opensymphony.xwork2.conversion.impl.XWorkConverter} if none + * is specified. + * + * @param from the source object + * @param to the target object + * @param context the action context we're running under + * @param exclusions collection of method names to excluded from copying ( can be null) + * @param inclusions collection of method names to included copying (can be null) + * note if exclusions AND inclusions are supplied and not null nothing will get copied. + */ + void copy(Object from, Object to, Map context, Collection exclusions, Collection inclusions); + + /** + * Looks for the real target with the specified property given a root Object which may be a + * CompoundRoot. + * + * @return the real target or null if no object can be found with the specified property + */ + Object getRealTarget(String property, Map context, Object root) throws ReflectionException; + + /** + * Sets the named property to the supplied value on the Object, + * + * @param name the name of the property to be set + * @param value the value to set into the named property + * @param o the object upon which to set the property + * @param context the context which may include the TypeConverter + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the properties + */ + void setProperty(String name, Object value, Object o, Map context, boolean throwPropertyExceptions); + + /** + * Sets the named property to the supplied value on the Object, defaults to not throwing + * property exceptions. + * + * @param name the name of the property to be set + * @param value the value to set into the named property + * @param o the object upon which to set the property + * @param context the context which may include the TypeConverter + */ + void setProperty(String name, Object value, Object o, Map context); + + /** + * Creates a Map with read properties for the given source object. + *

+ * If the source object does not have a read property (i.e. write-only) then + * the property is added to the map with the value here is no read method for property-name. + * + * @param source the source object. + * @return a Map with (key = read property name, value = value of read property). + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + Map getBeanMap(Object source) throws IntrospectionException, ReflectionException; + + /** + * Evaluates the given OGNL expression to extract a value from the given root + * object in a given context + * + * @param expression the OGNL expression to be parsed + * @param context the naming context for the evaluation + * @param root the root object for the OGNL expression + * @return the result of evaluating the expression + */ + Object getValue( String expression, Map context, Object root ) throws ReflectionException; + + /** + * Evaluates the given OGNL expression to insert a value into the object graph + * rooted at the given root object given the context. + * + * @param expression the OGNL expression to be parsed + * @param root the root object for the OGNL expression + * @param context the naming context for the evaluation + * @param value the value to insert into the object graph + */ + void setValue( String expression, Map context, Object root, Object value ) throws ReflectionException; + + /** + * Get's the java beans property descriptors for the given source. + * + * @param source the source object. + * @return property descriptors. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + PropertyDescriptor[] getPropertyDescriptors(Object source) throws IntrospectionException; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java new file mode 100644 index 000000000..a05b7d606 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java @@ -0,0 +1,10 @@ +package com.opensymphony.xwork2.util.reflection; + +import com.opensymphony.xwork2.ActionContext; + +public class ReflectionProviderFactory { + + public static ReflectionProvider getInstance() { + return ActionContext.getContext().getContainer().getInstance(ReflectionProvider.class); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java new file mode 100644 index 000000000..6650817cc --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java @@ -0,0 +1,87 @@ +/* + * 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.validator; + +import java.util.List; + +/** + * ActionValidatorManager is the main interface for validation managers (regular and annotation based). + * + * @author Rainer Hermanns + */ +public interface ActionValidatorManager { + + /** + * Returns a list of validators for the given class, context, and method. This is the primary + * lookup method for validators. + * + * @param clazz the class to lookup. + * @param context the context of the action class - can be null. + * @param method the name of the method being invoked on the action - can be null. + * @return a list of all validators for the given class and context. + */ + List getValidators(Class clazz, String context, String method); + + /** + * Returns a list of validators for the given class and context. This is the primary + * lookup method for validators. + * + * @param clazz the class to lookup. + * @param context the context of the action class - can be null. + * @return a list of all validators for the given class and context. + */ + List getValidators(Class clazz, String context); + + /** + * Validates the given object using action and its context. + * + * @param object the action to validate. + * @param context the action's context. + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context) throws ValidationException; + + /** + * Validates an action give its context and a validation context. + * + * @param object the action to validate. + * @param context the action's context. + * @param validatorContext the validation context to use + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException; + + /** + * Validates the given object using an action, its context, and the name of the method being invoked on the action. + * + * @param object the action to validate. + * @param context the action's context. + * @param method the name of the method being invoked on the action - can be null. + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context, String method) throws ValidationException; + + /** + * Validates an action give its context and a validation context. + * + * @param object the action to validate. + * @param context the action's context. + * @param validatorContext the validation context to use + * @param method the name of the method being invoked on the action - can be null. + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java new file mode 100644 index 000000000..844f78244 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java @@ -0,0 +1,410 @@ +/* + * 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.validator; + + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +/** + * AnnotationActionValidatorManager is the entry point into XWork's annotations-based validator framework. + * Validation rules are specified as annotations within the source files. + * + * @author Rainer Hermanns + * @author jepjep + */ +public class AnnotationActionValidatorManager implements ActionValidatorManager { + + /** + * The file suffix for any validation file. + */ + protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml"; + + private final Map> validatorCache = Collections.synchronizedMap(new HashMap>()); + private final Map> validatorFileCache = Collections.synchronizedMap(new HashMap>()); + private static final Logger LOG = LoggerFactory.getLogger(AnnotationActionValidatorManager.class); + + private ValidatorFactory validatorFactory; + private ValidatorFileParser validatorFileParser; + + @Inject + public void setValidatorFactory(ValidatorFactory fac) { + this.validatorFactory = fac; + } + + @Inject + public void setValidatorFileParser(ValidatorFileParser parser) { + this.validatorFileParser = parser; + } + + public synchronized List getValidators(Class clazz, String context) { + return getValidators(clazz, context, null); + } + + public synchronized List getValidators(Class clazz, String context, String method) { + final String validatorKey = buildValidatorKey(clazz); + + if (validatorCache.containsKey(validatorKey)) { + if (FileManager.isReloadingConfigs()) { + validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null)); + } + } else { + validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null)); + } + + // get the set of validator configs + List cfgs = validatorCache.get(validatorKey); + + ValueStack stack = ActionContext.getContext().getValueStack(); + + // create clean instances of the validators for the caller's use + ArrayList validators = new ArrayList(cfgs.size()); + for (ValidatorConfig cfg : cfgs) { + if (method == null || method.equals(cfg.getParams().get("methodName"))) { + Validator validator = validatorFactory.getValidator( + new ValidatorConfig.Builder(cfg) + .removeParam("methodName") + .build()); + validator.setValidatorType(cfg.getType()); + validator.setValueStack(stack); + validators.add(validator); + } + } + + return validators; + } + + public void validate(Object object, String context) throws ValidationException { + validate(object, context, (String) null); + } + + public void validate(Object object, String context, String method) throws ValidationException { + ValidatorContext validatorContext = new DelegatingValidatorContext(object); + validate(object, context, validatorContext, method); + } + + public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException { + validate(object, context, validatorContext, null); + } + + public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException { + List validators = getValidators(object.getClass(), context, method); + Set shortcircuitedFields = null; + + for (final Validator validator: validators) { + try { + validator.setValidatorContext(validatorContext); + + if (LOG.isDebugEnabled()) { + LOG.debug("Running validator: " + validator + " for object " + object + " and method " + method); + } + + FieldValidator fValidator = null; + String fullFieldName = null; + + if (validator instanceof FieldValidator) { + fValidator = (FieldValidator) validator; + fullFieldName = new InternalValidatorContextWrapper(fValidator.getValidatorContext()).getFullFieldName(fValidator.getFieldName()); + + if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Short-circuited, skipping"); + } + + continue; + } + } + + if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) + { + // get number of existing errors + List errs = null; + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection fieldErrors = validatorContext.getFieldErrors().get(fullFieldName); + + if (fieldErrors != null) { + errs = new ArrayList(fieldErrors); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection actionErrors = validatorContext.getActionErrors(); + + if (actionErrors != null) { + errs = new ArrayList(actionErrors); + } + } + + validator.validate(object); + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection errCol = validatorContext.getFieldErrors().get(fullFieldName); + + if ((errCol != null) && !errCol.equals(errs)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Short-circuiting on field validation"); + } + + if (shortcircuitedFields == null) { + shortcircuitedFields = new TreeSet(); + } + + shortcircuitedFields.add(fullFieldName); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection errCol = validatorContext.getActionErrors(); + + if ((errCol != null) && !errCol.equals(errs)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Short-circuiting"); + } + + break; + } + } + + continue; + } + + validator.validate(object); + } finally { + validator.setValidatorContext( null ); + } + + } + } + + /** + * Builds a key for validators - used when caching validators. + * + * @param clazz the action. + * @return a validator key which is the class name plus context. + */ + protected static String buildValidatorKey(Class clazz) { + ActionInvocation invocation = ActionContext.getContext().getActionInvocation(); + ActionProxy proxy = invocation.getProxy(); + + //the key needs to use the name of the action from the config file, + //instead of the url, so wild card actions will have the same validator + //see WW-2996 + StringBuilder sb = new StringBuilder(clazz.getName()); + sb.append("/"); + sb.append(proxy.getConfig().getName()); + sb.append("|"); + sb.append(proxy.getMethod()); + return sb.toString(); + } + + private List buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) { + String fileName = aClass.getName().replace('.', '/') + "-" + context.replace('/', '-') + VALIDATION_CONFIG_SUFFIX; + + return loadFile(fileName, aClass, checkFile); + } + + + protected List buildClassValidatorConfigs(Class aClass, boolean checkFile) { + + String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX; + + List result = new ArrayList(loadFile(fileName, aClass, checkFile)); + + AnnotationValidationConfigurationBuilder builder = new AnnotationValidationConfigurationBuilder(validatorFactory); + + List annotationResult = new ArrayList(builder.buildAnnotationClassValidatorConfigs(aClass)); + + result.addAll(annotationResult); + + return result; + + } + + /** + *

This method 'collects' all the validator configurations for a given + * action invocation.

+ *

+ *

It will traverse up the class hierarchy looking for validators for every super class + * and directly implemented interface of the current action, as well as adding validators for + * any alias of this invocation. Nifty!

+ *

+ *

Given the following class structure: + *

+     *   interface Thing;
+     *   interface Animal extends Thing;
+     *   interface Quadraped extends Animal;
+     *   class AnimalImpl implements Animal;
+     *   class QuadrapedImpl extends AnimalImpl implements Quadraped;
+     *   class Dog extends QuadrapedImpl;
+     * 

+ *

+ *

This method will look for the following config files for Dog: + *

+     *   Animal
+     *   Animal-context
+     *   AnimalImpl
+     *   AnimalImpl-context
+     *   Quadraped
+     *   Quadraped-context
+     *   QuadrapedImpl
+     *   QuadrapedImpl-context
+     *   Dog
+     *   Dog-context
+     * 

+ *

+ *

Note that the validation rules for Thing is never looked for because no class in the + * hierarchy directly implements Thing.

+ * + * @param clazz the Class to look up validators for. + * @param context the context to use when looking up validators. + * @param checkFile true if the validation config file should be checked to see if it has been + * updated. + * @param checked the set of previously checked class-contexts, null if none have been checked + * @return a list of validator configs for the given class and context. + */ + private List buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set checked) { + List validatorConfigs = new ArrayList(); + + if (checked == null) { + checked = new TreeSet(); + } else if (checked.contains(clazz.getName())) { + return validatorConfigs; + } + + if (clazz.isInterface()) { + Class[] interfaces = clazz.getInterfaces(); + + for (Class anInterface : interfaces) { + validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked)); + } + } else { + if (!clazz.equals(Object.class)) { + validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked)); + } + } + + // look for validators for implemented interfaces + Class[] interfaces = clazz.getInterfaces(); + + for (Class anInterface1 : interfaces) { + if (checked.contains(anInterface1.getName())) { + continue; + } + + validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile)); + + if (context != null) { + validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile)); + } + + checked.add(anInterface1.getName()); + } + + validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile)); + + if (context != null) { + validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile)); + } + + checked.add(clazz.getName()); + + return validatorConfigs; + } + + private List loadFile(String fileName, Class clazz, boolean checkFile) { + List retList = Collections.emptyList(); + + if ((checkFile && FileManager.fileNeedsReloading(fileName, clazz)) || !validatorFileCache.containsKey(fileName)) { + InputStream is = null; + + try { + is = FileManager.loadFile(fileName, clazz); + + if (is != null) { + retList = new ArrayList(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName)); + } + } catch (Exception e) { + LOG.error("Caught exception while loading file " + fileName, e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOG.error("Unable to close input stream for " + fileName, e); + } + } + } + + validatorFileCache.put(fileName, retList); + } else { + retList = validatorFileCache.get(fileName); + } + + return retList; + } + + + + /** + * An {@link com.opensymphony.xwork2.validator.ValidatorContext} wrapper that + * returns the full field name + * {@link com.opensymphony.xwork2.validator.AbstractActionValidatorManager.InternalValidatorContextWrapper#getFullFieldName(String)} + * by consulting it's parent if its an {@link com.opensymphony.xwork2.validator.validators.VisitorFieldValidator.AppendingValidatorContext}. + *

+ * Eg. if we have nested Visitor + * AddressVisitor nested inside PersonVisitor, when using the normal #getFullFieldName, we will get + * "address.somefield", we lost the parent, with this wrapper, we will get "person.address.somefield". + * This is so that the key is used to register errors, so that we don't screw up short-curcuit feature + * when using nested visitor. See XW-571 (nested visitor validators break short-circuit functionality) + * at http://jira.opensymphony.com/browse/XW-571 + */ + protected class InternalValidatorContextWrapper { + private ValidatorContext validatorContext = null; + + InternalValidatorContextWrapper(ValidatorContext validatorContext) { + this.validatorContext = validatorContext; + } + + /** + * Get the full field name by consulting the parent, so that when we are using nested visitors ( + * visitor nested inside visitor etc.) we still get the full field name including its parents. + * See XW-571 for more details. + * @param field + * @return String + */ + public String getFullFieldName(String field) { + if (validatorContext instanceof VisitorFieldValidator.AppendingValidatorContext) { + VisitorFieldValidator.AppendingValidatorContext appendingValidatorContext = + (VisitorFieldValidator.AppendingValidatorContext) validatorContext; + return appendingValidatorContext.getFullFieldNameFromParent(field); + } + return validatorContext.getFullFieldName(field); + } + + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java new file mode 100644 index 000000000..c0ca47ea1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java @@ -0,0 +1,816 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.validator.annotations.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * AnnotationValidationConfigurationBuilder + * + * @author Rainer Hermanns + * @author jepjep + * @version $Id$ + */ +public class AnnotationValidationConfigurationBuilder { + + private static final Pattern SETTER_PATTERN = Pattern.compile("set([A-Z][A-Za-z0-9]*)$"); + private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is|has)([A-Z][A-Za-z0-9]*)$"); + + private ValidatorFactory validatorFactory; + + public AnnotationValidationConfigurationBuilder(ValidatorFactory fac) { + this.validatorFactory = fac; + } + + private List processAnnotations(Object o) { + + List result = new ArrayList(); + + String fieldName = null; + String methodName = null; + + Annotation[] annotations = null; + + if (o instanceof Class) { + Class clazz = (Class) o; + annotations = clazz.getAnnotations(); + } + + if (o instanceof Method) { + Method method = (Method) o; + fieldName = resolvePropertyName(method); + methodName = method.getName(); + + annotations = method.getAnnotations(); + } + + if (annotations != null) { + for (Annotation a : annotations) { + + // Process collection of custom validations + if (a instanceof Validations) { + processValidationAnnotation(a, fieldName, methodName, result); + + } + + // Process single custom validator + if (a instanceof Validation) { + Validation v = (Validation) a; + if ( v.validations() != null ) { + for ( Validations val: v.validations()) { + processValidationAnnotation(val , fieldName, methodName, result); + } + } + + } + // Process single custom validator + else if (a instanceof ExpressionValidator) { + ExpressionValidator v = (ExpressionValidator) a; + ValidatorConfig temp = processExpressionValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + + } + // Process single custom validator + else if (a instanceof CustomValidator) { + CustomValidator v = (CustomValidator) a; + ValidatorConfig temp = processCustomValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + + } + + // Process ConversionErrorFieldValidator + else if ( a instanceof ConversionErrorFieldValidator) { + ConversionErrorFieldValidator v = (ConversionErrorFieldValidator) a; + ValidatorConfig temp = processConversionErrorFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process DateRangeFieldValidator + else if ( a instanceof DateRangeFieldValidator) { + DateRangeFieldValidator v = (DateRangeFieldValidator) a; + ValidatorConfig temp = processDateRangeFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process EmailValidator + else if ( a instanceof EmailValidator) { + EmailValidator v = (EmailValidator) a; + ValidatorConfig temp = processEmailValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process FieldExpressionValidator + else if ( a instanceof FieldExpressionValidator) { + FieldExpressionValidator v = (FieldExpressionValidator) a; + ValidatorConfig temp = processFieldExpressionValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process IntRangeFieldValidator + else if ( a instanceof IntRangeFieldValidator) { + IntRangeFieldValidator v = (IntRangeFieldValidator) a; + ValidatorConfig temp = processIntRangeFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process DoubleRangeFieldValidator + else if ( a instanceof DoubleRangeFieldValidator) { + DoubleRangeFieldValidator v = (DoubleRangeFieldValidator) a; + ValidatorConfig temp = processDoubleRangeFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process RequiredFieldValidator + else if ( a instanceof RequiredFieldValidator) { + RequiredFieldValidator v = (RequiredFieldValidator) a; + ValidatorConfig temp = processRequiredFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process RequiredStringValidator + else if ( a instanceof RequiredStringValidator) { + RequiredStringValidator v = (RequiredStringValidator) a; + ValidatorConfig temp = processRequiredStringValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process StringLengthFieldValidator + else if ( a instanceof StringLengthFieldValidator) { + StringLengthFieldValidator v = (StringLengthFieldValidator) a; + ValidatorConfig temp = processStringLengthFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + } + // Process UrlValidator + else if ( a instanceof UrlValidator) { + UrlValidator v = (UrlValidator) a; + ValidatorConfig temp = processUrlValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process ConditionalVisitorFieldValidator + else if ( a instanceof ConditionalVisitorFieldValidator) { + ConditionalVisitorFieldValidator v = (ConditionalVisitorFieldValidator) a; + ValidatorConfig temp = processConditionalVisitorFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process VisitorFieldValidator + else if ( a instanceof VisitorFieldValidator) { + VisitorFieldValidator v = (VisitorFieldValidator) a; + ValidatorConfig temp = processVisitorFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + // Process RegexFieldValidator + else if ( a instanceof RegexFieldValidator) { + RegexFieldValidator v = (RegexFieldValidator) a; + ValidatorConfig temp = processRegexFieldValidatorAnnotation(v, fieldName, methodName); + if ( temp != null) { + result.add(temp); + } + + } + } + } + return result; + } + + private void processValidationAnnotation(Annotation a, String fieldName, String methodName, List result) { + Validations validations = (Validations) a; + CustomValidator[] cv = validations.customValidators(); + if ( cv != null ) { + for (CustomValidator v : cv) { + ValidatorConfig temp = processCustomValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + ExpressionValidator[] ev = validations.expressions(); + if ( ev != null ) { + for (ExpressionValidator v : ev) { + ValidatorConfig temp = processExpressionValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + ConversionErrorFieldValidator[] cef = validations.conversionErrorFields(); + if ( cef != null ) { + for (ConversionErrorFieldValidator v : cef) { + ValidatorConfig temp = processConversionErrorFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + DateRangeFieldValidator[] drfv = validations.dateRangeFields(); + if ( drfv != null ) { + for (DateRangeFieldValidator v : drfv) { + ValidatorConfig temp = processDateRangeFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + EmailValidator[] emv = validations.emails(); + if ( emv != null ) { + for (EmailValidator v : emv) { + ValidatorConfig temp = processEmailValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + FieldExpressionValidator[] fev = validations.fieldExpressions(); + if ( fev != null ) { + for (FieldExpressionValidator v : fev) { + ValidatorConfig temp = processFieldExpressionValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + IntRangeFieldValidator[] irfv = validations.intRangeFields(); + if ( irfv != null ) { + for (IntRangeFieldValidator v : irfv) { + ValidatorConfig temp = processIntRangeFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + RegexFieldValidator[] rfv = validations.regexFields(); + if ( rfv != null ) { + for (RegexFieldValidator v : rfv) { + ValidatorConfig temp = processRegexFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + RequiredFieldValidator[] rv = validations.requiredFields(); + if ( rv != null ) { + for (RequiredFieldValidator v : rv) { + ValidatorConfig temp = processRequiredFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + RequiredStringValidator[] rsv = validations.requiredStrings(); + if ( rsv != null ) { + for (RequiredStringValidator v : rsv) { + ValidatorConfig temp = processRequiredStringValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + StringLengthFieldValidator[] slfv = validations.stringLengthFields(); + if ( slfv != null ) { + for (StringLengthFieldValidator v : slfv) { + ValidatorConfig temp = processStringLengthFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + UrlValidator[] uv = validations.urls(); + if ( uv != null ) { + for (UrlValidator v : uv) { + ValidatorConfig temp = processUrlValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + ConditionalVisitorFieldValidator[] cvfv = validations.conditionalVisitorFields(); + if ( cvfv != null ) { + for (ConditionalVisitorFieldValidator v : cvfv) { + ValidatorConfig temp = processConditionalVisitorFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + VisitorFieldValidator[] vfv = validations.visitorFields(); + if ( vfv != null ) { + for (VisitorFieldValidator v : vfv) { + ValidatorConfig temp = processVisitorFieldValidatorAnnotation(v, fieldName, methodName); + if (temp != null) { + result.add(temp); + } + } + } + } + + private ValidatorConfig processExpressionValidatorAnnotation(ExpressionValidator v, String fieldName, String methodName) { + String validatorType = "expression"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } + + params.put("expression", v.expression()); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + + } + + private ValidatorConfig processCustomValidatorAnnotation(CustomValidator v, String fieldName, String methodName) { + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + + String validatorType = v.type(); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + + Annotation[] recursedAnnotations = v.parameters(); + + if ( recursedAnnotations != null ) { + for (Annotation a2 : recursedAnnotations) { + + if (a2 instanceof ValidationParameter) { + + ValidationParameter parameter = (ValidationParameter) a2; + String parameterName = parameter.name(); + String parameterValue = parameter.value(); + params.put(parameterName, parameterValue); + } + + } + } + + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processRegexFieldValidatorAnnotation(RegexFieldValidator v, String fieldName, String methodName) { + String validatorType = "regex"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + params.put("expression", v.expression()); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processConditionalVisitorFieldValidatorAnnotation(ConditionalVisitorFieldValidator v, String fieldName, String methodName) { + String validatorType = "conditionalvisitor"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + params.put("expression", v.expression()); + params.put("context", v.context()); + params.put("appendPrefix", String.valueOf(v.appendPrefix())); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + + + private ValidatorConfig processVisitorFieldValidatorAnnotation(VisitorFieldValidator v, String fieldName, String methodName) { + + String validatorType = "visitor"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + params.put("context", v.context()); + params.put("appendPrefix", String.valueOf(v.appendPrefix())); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processUrlValidatorAnnotation(UrlValidator v, String fieldName, String methodName) { + String validatorType = "url"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processStringLengthFieldValidatorAnnotation(StringLengthFieldValidator v, String fieldName, String methodName) { + String validatorType = "stringlength"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + if ( v.maxLength() != null && v.maxLength().length() > 0) { + params.put("maxLength", v.maxLength()); + } + if ( v.minLength() != null && v.minLength().length() > 0) { + params.put("minLength", v.minLength()); + } + params.put("trim", String.valueOf(v.trim())); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private Date parseDateString(String value) { + + SimpleDateFormat d1 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, Locale.getDefault()); + SimpleDateFormat d2 = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, Locale.getDefault()); + SimpleDateFormat d3 = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault()); + SimpleDateFormat[] dfs = {d1, d2, d3}; + DateFormat df = null; + for (SimpleDateFormat df1 : dfs) { + try { + Date check = df1.parse(value); + df = df1; + if (check != null) { + return check; + } + } + catch (ParseException ignore) { + } + } + return null; + + } + + private ValidatorConfig processRequiredStringValidatorAnnotation(RequiredStringValidator v, String fieldName, String methodName) { + String validatorType = "requiredstring"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + params.put("trim", String.valueOf(v.trim())); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processRequiredFieldValidatorAnnotation(RequiredFieldValidator v, String fieldName, String methodName) { + String validatorType = "required"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processIntRangeFieldValidatorAnnotation(IntRangeFieldValidator v, String fieldName, String methodName) { + String validatorType = "int"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + if ( v.min() != null && v.min().length() > 0) { + params.put("min", v.min()); + } + if ( v.max() != null && v.max().length() > 0) { + params.put("max", v.max()); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processDoubleRangeFieldValidatorAnnotation(DoubleRangeFieldValidator v, String fieldName, String methodName) { + String validatorType = "double"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + if ( v.minInclusive() != null && v.minInclusive().length() > 0) { + params.put("minInclusive", v.minInclusive()); + } + if ( v.maxInclusive() != null && v.maxInclusive().length() > 0) { + params.put("maxInclusive", v.maxInclusive()); + } + + if ( v.minExclusive() != null && v.minExclusive().length() > 0) { + params.put("minExclusive", v.minExclusive()); + } + if ( v.maxExclusive() != null && v.maxExclusive().length() > 0) { + params.put("maxExclusive", v.maxExclusive()); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processFieldExpressionValidatorAnnotation(FieldExpressionValidator v, String fieldName, String methodName) { + String validatorType = "fieldexpression"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + params.put("expression", v.expression()); + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processEmailValidatorAnnotation(EmailValidator v, String fieldName, String methodName) { + String validatorType = "email"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processDateRangeFieldValidatorAnnotation(DateRangeFieldValidator v, String fieldName, String methodName) { + String validatorType = "date"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + if ( v.min() != null && v.min().length() > 0) { + final Date minDate = parseDateString(v.min()); + params.put("min", String.valueOf(minDate == null ? v.min() : minDate)); + } + if ( v.max() != null && v.max().length() > 0) { + final Date maxDate = parseDateString(v.max()); + params.put("max", String.valueOf(maxDate == null ? v.max() : maxDate)); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + private ValidatorConfig processConversionErrorFieldValidatorAnnotation(ConversionErrorFieldValidator v, String fieldName, String methodName) { + String validatorType = "conversion"; + + Map params = new HashMap(); + + if (fieldName != null) { + params.put("fieldName", fieldName); + } else if (v.fieldName() != null && v.fieldName().length() > 0 ) { + params.put("fieldName", v.fieldName()); + } + + validatorFactory.lookupRegisteredValidatorType(validatorType); + return new ValidatorConfig.Builder(validatorType) + .addParams(params) + .addParam("methodName", methodName) + .shortCircuit(v.shortCircuit()) + .defaultMessage(v.message()) + .messageKey(v.key()) + .build(); + } + + public List buildAnnotationClassValidatorConfigs(Class aClass) { + + List result = new ArrayList(); + + List temp = processAnnotations(aClass); + if (temp != null) { + result.addAll(temp); + } + + Method[] methods = aClass.getDeclaredMethods(); + + if ( methods != null ) { + for (Method method : methods) { + temp = processAnnotations(method); + if (temp != null) { + result.addAll(temp); + } + } + } + + return result; + + } + + /** + * Returns the property name for a method. + * This method is independant from property fields. + * + * @param method The method to get the property name for. + * @return the property name for given method; null if non could be resolved. + */ + public String resolvePropertyName(Method method) { + + Matcher matcher = SETTER_PATTERN.matcher(method.getName()); + if (matcher.matches() && method.getParameterTypes().length == 1) { + String raw = matcher.group(1); + return raw.substring(0, 1).toLowerCase() + raw.substring(1); + } + + matcher = GETTER_PATTERN.matcher(method.getName()); + if (matcher.matches() && method.getParameterTypes().length == 0) { + String raw = matcher.group(2); + return raw.substring(0, 1).toLowerCase() + raw.substring(1); + } + + return null; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java new file mode 100644 index 000000000..0042e0a67 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java @@ -0,0 +1,387 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + + +/** + * This is the entry point into XWork's rule-based validation framework. + *

+ * Validation rules are specified in XML configuration files named className-contextName-validation.xml where + * className is the name of the class the configuration is for and -contextName is optional + * (contextName is an arbitrary key that is used to look up additional validation rules for a + * specific context). + * + * @author Jason Carreira + * @author Mark Woon + * @author James House + * @author Rainer Hermanns + */ +public class DefaultActionValidatorManager implements ActionValidatorManager { + + /** The file suffix for any validation file. */ + protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml"; + + private final Map> validatorCache = Collections.synchronizedMap(new HashMap>()); + private final Map> validatorFileCache = Collections.synchronizedMap(new HashMap>()); + private final Logger LOG = LoggerFactory.getLogger(DefaultActionValidatorManager.class); + private ValidatorFactory validatorFactory; + private ValidatorFileParser validatorFileParser; + + @Inject + public void setValidatorFileParser(ValidatorFileParser parser) { + this.validatorFileParser = parser; + } + + @Inject + public void setValidatorFactory(ValidatorFactory fac) { + this.validatorFactory = fac; + } + + public synchronized List getValidators(Class clazz, String context) { + return getValidators(clazz, context, null); + } + + public synchronized List getValidators(Class clazz, String context, String method) { + final String validatorKey = buildValidatorKey(clazz, context); + + if (validatorCache.containsKey(validatorKey)) { + if (FileManager.isReloadingConfigs()) { + validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null)); + } + } else { + validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null)); + } + ValueStack stack = ActionContext.getContext().getValueStack(); + + // get the set of validator configs + List cfgs = validatorCache.get(validatorKey); + + // create clean instances of the validators for the caller's use + ArrayList validators = new ArrayList(cfgs.size()); + for (ValidatorConfig cfg : cfgs) { + if (method == null || method.equals(cfg.getParams().get("methodName"))) { + Validator validator = validatorFactory.getValidator(cfg); + validator.setValidatorType(cfg.getType()); + validator.setValueStack(stack); + validators.add(validator); + } + } + return validators; + } + + public void validate(Object object, String context) throws ValidationException { + validate(object, context, (String) null); + } + + public void validate(Object object, String context, String method) throws ValidationException { + ValidatorContext validatorContext = new DelegatingValidatorContext(object); + validate(object, context, validatorContext, method); + } + + public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException { + validate(object, context, validatorContext, null); + } + + public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException { + List validators = getValidators(object.getClass(), context, method); + Set shortcircuitedFields = null; + + for (final Validator validator : validators) { + try { + validator.setValidatorContext(validatorContext); + + if (LOG.isDebugEnabled()) { + LOG.debug("Running validator: " + validator + " for object " + object + " and method " + method); + } + + FieldValidator fValidator = null; + String fullFieldName = null; + + if (validator instanceof FieldValidator) { + fValidator = (FieldValidator) validator; + fullFieldName = new InternalValidatorContextWrapper(fValidator.getValidatorContext()).getFullFieldName(fValidator.getFieldName()); + + // This is pretty crap, but needed to support short-circuited validations on nested visited objects + if (validatorContext instanceof VisitorFieldValidator.AppendingValidatorContext) { + VisitorFieldValidator.AppendingValidatorContext appendingValidatorContext = + (VisitorFieldValidator.AppendingValidatorContext) validatorContext; + fullFieldName = appendingValidatorContext.getFullFieldNameFromParent(fValidator.getFieldName()); + } + + if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Short-circuited, skipping"); + } + + continue; + } + } + + if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) { + // get number of existing errors + List errs = null; + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection fieldErrors = validatorContext.getFieldErrors().get(fullFieldName); + + if (fieldErrors != null) { + errs = new ArrayList(fieldErrors); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection actionErrors = validatorContext.getActionErrors(); + + if (actionErrors != null) { + errs = new ArrayList(actionErrors); + } + } + + validator.validate(object); + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection errCol = validatorContext.getFieldErrors().get(fullFieldName); + + if ((errCol != null) && !errCol.equals(errs)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Short-circuiting on field validation"); + } + + if (shortcircuitedFields == null) { + shortcircuitedFields = new TreeSet(); + } + + shortcircuitedFields.add(fullFieldName); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection errCol = validatorContext.getActionErrors(); + + if ((errCol != null) && !errCol.equals(errs)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Short-circuiting"); + } + + break; + } + } + + continue; + } + + validator.validate(object); + } + finally { + validator.setValidatorContext(null); + } + } + } + + /** + * Builds a key for validators - used when caching validators. + * + * @param clazz the action. + * @param context the action's context. + * @return a validator key which is the class name plus context. + */ + protected static String buildValidatorKey(Class clazz, String context) { + StringBuilder sb = new StringBuilder(clazz.getName()); + sb.append("/"); + sb.append(context); + return sb.toString(); + } + + private List buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) { + String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX; + + return loadFile(fileName, aClass, checkFile); + } + + private List buildClassValidatorConfigs(Class aClass, boolean checkFile) { + String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX; + + return loadFile(fileName, aClass, checkFile); + } + + /** + *

This method 'collects' all the validator configurations for a given + * action invocation.

+ * + *

It will traverse up the class hierarchy looking for validators for every super class + * and directly implemented interface of the current action, as well as adding validators for + * any alias of this invocation. Nifty!

+ * + *

Given the following class structure: + *

+     *   interface Thing;
+     *   interface Animal extends Thing;
+     *   interface Quadraped extends Animal;
+     *   class AnimalImpl implements Animal;
+     *   class QuadrapedImpl extends AnimalImpl implements Quadraped;
+     *   class Dog extends QuadrapedImpl;
+     * 

+ * + *

This method will look for the following config files for Dog: + *

+     *   Animal
+     *   Animal-context
+     *   AnimalImpl
+     *   AnimalImpl-context
+     *   Quadraped
+     *   Quadraped-context
+     *   QuadrapedImpl
+     *   QuadrapedImpl-context
+     *   Dog
+     *   Dog-context
+     * 

+ * + *

Note that the validation rules for Thing is never looked for because no class in the + * hierarchy directly implements Thing.

+ * + * @param clazz the Class to look up validators for. + * @param context the context to use when looking up validators. + * @param checkFile true if the validation config file should be checked to see if it has been + * updated. + * @param checked the set of previously checked class-contexts, null if none have been checked + * @return a list of validator configs for the given class and context. + */ + private List buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set checked) { + List validatorConfigs = new ArrayList(); + + if (checked == null) { + checked = new TreeSet(); + } else if (checked.contains(clazz.getName())) { + return validatorConfigs; + } + + if (clazz.isInterface()) { + for (Class anInterface : clazz.getInterfaces()) { + validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked)); + } + } else { + if (!clazz.equals(Object.class)) { + validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked)); + } + } + + // look for validators for implemented interfaces + for (Class anInterface1 : clazz.getInterfaces()) { + if (checked.contains(anInterface1.getName())) { + continue; + } + + validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile)); + + if (context != null) { + validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile)); + } + + checked.add(anInterface1.getName()); + } + + validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile)); + + if (context != null) { + validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile)); + } + + checked.add(clazz.getName()); + + return validatorConfigs; + } + + private List loadFile(String fileName, Class clazz, boolean checkFile) { + List retList = Collections.emptyList(); + if ((checkFile && FileManager.fileNeedsReloading(fileName, clazz)) || !validatorFileCache.containsKey(fileName)) { + InputStream is = null; + + try { + is = FileManager.loadFile(fileName, clazz); + + if (is != null) { + retList = new ArrayList(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName)); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOG.error("Unable to close input stream for " + fileName, e); + } + } + } + + validatorFileCache.put(fileName, retList); + } else { + retList = validatorFileCache.get(fileName); + } + + return retList; + } + + + /** + * An {@link com.opensymphony.xwork2.validator.ValidatorContext} wrapper that + * returns the full field name + * {@link InternalValidatorContextWrapper#getFullFieldName(String)} + * by consulting it's parent if its an {@link com.opensymphony.xwork2.validator.validators.VisitorFieldValidator.AppendingValidatorContext}. + *

+ * Eg. if we have nested Visitor + * AddressVisitor nested inside PersonVisitor, when using the normal #getFullFieldName, we will get + * "address.somefield", we lost the parent, with this wrapper, we will get "person.address.somefield". + * This is so that the key is used to register errors, so that we don't screw up short-curcuit feature + * when using nested visitor. See XW-571 (nested visitor validators break short-circuit functionality) + * at http://jira.opensymphony.com/browse/XW-571 + */ + protected class InternalValidatorContextWrapper { + private ValidatorContext validatorContext = null; + + InternalValidatorContextWrapper(ValidatorContext validatorContext) { + this.validatorContext = validatorContext; + } + + /** + * Get the full field name by consulting the parent, so that when we are using nested visitors ( + * visitor nested inside visitor etc.) we still get the full field name including its parents. + * See XW-571 for more details. + * @param field The field name + * @return String + */ + public String getFullFieldName(String field) { + if (validatorContext instanceof VisitorFieldValidator.AppendingValidatorContext) { + VisitorFieldValidator.AppendingValidatorContext appendingValidatorContext = + (VisitorFieldValidator.AppendingValidatorContext) validatorContext; + return appendingValidatorContext.getFullFieldNameFromParent(field); + } + return validatorContext.getFullFieldName(field); + } + + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java new file mode 100644 index 000000000..70e55440b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java @@ -0,0 +1,198 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.*; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipEntry; + + +/** + * Default validator factory + * + * @version $Date$ $Id$ + * @author Jason Carreira + * @author James House + */ +public class DefaultValidatorFactory implements ValidatorFactory { + + protected Map validators = new HashMap(); + private static Logger LOG = LoggerFactory.getLogger(DefaultValidatorFactory.class); + protected ObjectFactory objectFactory; + protected ValidatorFileParser validatorFileParser; + + @Inject + public DefaultValidatorFactory(@Inject ObjectFactory objectFactory, @Inject ValidatorFileParser parser) { + this.objectFactory = objectFactory; + this.validatorFileParser = parser; + parseValidators(); + } + + public Validator getValidator(ValidatorConfig cfg) { + + String className = lookupRegisteredValidatorType(cfg.getType()); + + Validator validator; + + try { + // instantiate the validator, and set configured parameters + //todo - can this use the ThreadLocal? + validator = objectFactory.buildValidator(className, cfg.getParams(), null); // ActionContext.getContext().getContextMap()); + } catch (Exception e) { + final String msg = "There was a problem creating a Validator of type " + className + " : caused by " + e.getMessage(); + throw new XWorkException(msg, e, cfg); + } + + // set other configured properties + validator.setMessageKey(cfg.getMessageKey()); + validator.setDefaultMessage(cfg.getDefaultMessage()); + validator.setMessageParameters(cfg.getMessageParams()); + if (validator instanceof ShortCircuitableValidator) { + ((ShortCircuitableValidator) validator).setShortCircuit(cfg.isShortCircuit()); + } + + return validator; + } + + public void registerValidator(String name, String className) { + if (LOG.isDebugEnabled()) { + LOG.debug("Registering validator of class " + className + " with name " + name); + } + + validators.put(name, className); + } + + public String lookupRegisteredValidatorType(String name) { + // lookup the validator class mapped to the type name + String className = validators.get(name); + + if (className == null) { + throw new IllegalArgumentException("There is no validator class mapped to the name " + name); + } + + return className; + } + + private void parseValidators() { + if (LOG.isDebugEnabled()) { + LOG.debug("Loading validator definitions."); + } + + List files = new ArrayList(); + try { + // Get custom validator configurations via the classpath + Iterator urls = ClassLoaderUtil.getResources("", DefaultValidatorFactory.class, false); + while (urls.hasNext()) { + URL u = urls.next(); + try { + URI uri = new URI(u.toExternalForm().replaceAll(" ", "%20")); + if (!uri.isOpaque() && "file".equalsIgnoreCase(uri.getScheme())) { + File f = new File(uri); + FilenameFilter filter = new FilenameFilter() { + public boolean accept(File file, String fileName) { + return fileName.contains("-validators.xml"); + } + }; + // First check if this is a directory + // If yes, then just do a "list" to get all files in this directory + // and match the filenames with *-validators.xml. If the filename + // matches then add to the list of files to be parsed + if (f.isDirectory()) { + try { + File[] ff = f.listFiles(filter); + if ( ff != null && ff.length > 0) { + files.addAll(Arrays.asList(ff)); + } + } catch (SecurityException se) { + LOG.error("Security Exception while accessing directory '" + f + "'", se); + } + + } else { + // If this is not a directory, then get hold of the inputstream. + // If its not a ZipInputStream, then create a ZipInputStream out + // of it. The intention is to allow nested jar files to be scanned + // for *-validators.xml. + // Ex: struts-app.jar -> MyApp.jar -> Login-validators.xml should be + // parsed and loaded. + ZipInputStream zipInputStream = null; + try { + InputStream inputStream = u.openStream(); + if (inputStream instanceof ZipInputStream) { + zipInputStream = (ZipInputStream) inputStream; + } else { + zipInputStream = new ZipInputStream(inputStream); + } + ZipEntry zipEntry = zipInputStream.getNextEntry(); + while (zipEntry != null) { + if (zipEntry.getName().endsWith("-validators.xml")) { + if (LOG.isTraceEnabled()) { + LOG.trace("Adding validator " + zipEntry.getName()); + } + files.add(new File(zipEntry.getName())); + } + zipEntry = zipInputStream.getNextEntry(); + } + } finally { + //cleanup + if (zipInputStream != null) { + zipInputStream.close(); + } + } + } + } + } catch (Exception ex) { + LOG.error("Unable to load #0", ex, u.toString()); + } + } + } catch (IOException e) { + throw new ConfigurationException("Unable to parse validators", e); + } + + // Parse default validator configurations + String resourceName = "com/opensymphony/xwork2/validator/validators/default.xml"; + retrieveValidatorConfiguration(resourceName); + + // Overwrite and extend defaults with application specific validator configurations + resourceName = "validators.xml"; + retrieveValidatorConfiguration(resourceName); + + // Add custom (plugin) specific validator configurations + for (File file : files) { + retrieveValidatorConfiguration(file.getName()); + } + } + + private void retrieveValidatorConfiguration(String resourceName) { + InputStream is = ClassLoaderUtil.getResourceAsStream(resourceName, DefaultValidatorFactory.class); + if (is != null) { + validatorFileParser.parseValidatorDefinitions(validators, is, resourceName); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java new file mode 100644 index 000000000..38be46802 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java @@ -0,0 +1,241 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.providers.XmlHelper; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.DomHelper; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.w3c.dom.*; +import org.xml.sax.InputSource; + +import java.io.InputStream; +import java.util.*; + + +/** + * Parse the validation file. (eg. MyAction-validation.xml, MyAction-actionAlias-validation.xml) + * to return a List of ValidatorConfig encapsulating the validator information. + * + * @author Jason Carreira + * @author James House + * @author tm_jee ( tm_jee (at) yahoo.co.uk ) + * @author Rob Harrop + * @author Rene Gielen + * @author Martin Gilday + * + * @see com.opensymphony.xwork2.validator.ValidatorConfig + */ +public class DefaultValidatorFileParser implements ValidatorFileParser { + + private static Logger LOG = LoggerFactory.getLogger(DefaultValidatorFileParser.class); + + static final String DEFAULT_MULTI_TEXTVALUE_SEPARATOR = " "; + static final String MULTI_TEXTVALUE_SEPARATOR_CONFIG_KEY = "xwork.validatorfileparser.multi_textvalue_separator"; + + private ObjectFactory objectFactory; + private String multiTextvalueSeparator=DEFAULT_MULTI_TEXTVALUE_SEPARATOR; + + @Inject(value=MULTI_TEXTVALUE_SEPARATOR_CONFIG_KEY, required = false) + public void setMultiTextvalueSeparator(String type) { + multiTextvalueSeparator = type; + } + + public String getMultiTextvalueSeparator() { + return multiTextvalueSeparator; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + public List parseActionValidatorConfigs(ValidatorFactory validatorFactory, InputStream is, final String resourceName) { + List validatorCfgs = new ArrayList(); + + InputSource in = new InputSource(is); + in.setSystemId(resourceName); + + Map dtdMappings = new HashMap(); + dtdMappings.put("-//OpenSymphony Group//XWork Validator 1.0//EN", "xwork-validator-1.0.dtd"); + dtdMappings.put("-//OpenSymphony Group//XWork Validator 1.0.2//EN", "xwork-validator-1.0.2.dtd"); + dtdMappings.put("-//OpenSymphony Group//XWork Validator 1.0.3//EN", "xwork-validator-1.0.3.dtd"); + + Document doc = DomHelper.parse(in, dtdMappings); + + if (doc != null) { + NodeList fieldNodes = doc.getElementsByTagName("field"); + + // BUG: xw-305: Let validator be parsed first and hence added to + // the beginning of list and therefore evaluated first, so short-circuting + // it will not cause field-level validator to be kicked off. + { + NodeList validatorNodes = doc.getElementsByTagName("validator"); + addValidatorConfigs(validatorFactory, validatorNodes, new HashMap(), validatorCfgs); + } + + for (int i = 0; i < fieldNodes.getLength(); i++) { + Element fieldElement = (Element) fieldNodes.item(i); + String fieldName = fieldElement.getAttribute("name"); + Map extraParams = new HashMap(); + extraParams.put("fieldName", fieldName); + + NodeList validatorNodes = fieldElement.getElementsByTagName("field-validator"); + addValidatorConfigs(validatorFactory, validatorNodes, extraParams, validatorCfgs); + } + } + + return validatorCfgs; + } + + + public void parseValidatorDefinitions(Map validators, InputStream is, String resourceName) { + + InputSource in = new InputSource(is); + in.setSystemId(resourceName); + + Map dtdMappings = new HashMap(); + dtdMappings.put("-//OpenSymphony Group//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd"); + + Document doc = DomHelper.parse(in, dtdMappings); + + if (doc != null) { + NodeList nodes = doc.getElementsByTagName("validator"); + + for (int i = 0; i < nodes.getLength(); i++) { + Element validatorElement = (Element) nodes.item(i); + String name = validatorElement.getAttribute("name"); + String className = validatorElement.getAttribute("class"); + + try { + // catch any problems here + objectFactory.buildValidator(className, new HashMap(), null); + validators.put(name, className); + } catch (Exception e) { + throw new ConfigurationException("Unable to load validator class " + className, e, validatorElement); + } + } + } + } + + /** + * Extract trimmed text value from the given DOM element, ignoring XML comments. Appends all CharacterData nodes + * and EntityReference nodes into a single String value, excluding Comment nodes. + * This method is based on a method originally found in DomUtils class of Springframework. + * + * @see org.w3c.dom.CharacterData + * @see org.w3c.dom.EntityReference + * @see org.w3c.dom.Comment + */ + public String getTextValue(Element valueEle) { + StringBuilder value = new StringBuilder(); + NodeList nl = valueEle.getChildNodes(); + boolean firstCDataFound = false; + for (int i = 0; i < nl.getLength(); i++) { + Node item = nl.item(i); + if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) { + final String nodeValue = item.getNodeValue(); + if (nodeValue != null) { + if (firstCDataFound) { + value.append(getMultiTextvalueSeparator()); + } else { + firstCDataFound = true; + } + value.append(nodeValue.trim()); + } + } + } + return value.toString().trim(); + } + + private void addValidatorConfigs(ValidatorFactory factory, NodeList validatorNodes, Map extraParams, List validatorCfgs) { + for (int j = 0; j < validatorNodes.getLength(); j++) { + Element validatorElement = (Element) validatorNodes.item(j); + String validatorType = validatorElement.getAttribute("type"); + Map params = new HashMap(extraParams); + + params.putAll(XmlHelper.getParams(validatorElement)); + + // ensure that the type is valid... + try { + factory.lookupRegisteredValidatorType(validatorType); + } catch (IllegalArgumentException ex) { + throw new ConfigurationException("Invalid validation type: " + validatorType, validatorElement); + } + + ValidatorConfig.Builder vCfg = new ValidatorConfig.Builder(validatorType) + .addParams(params) + .location(DomHelper.getLocationObject(validatorElement)) + .shortCircuit(Boolean.valueOf(validatorElement.getAttribute("short-circuit")).booleanValue()); + + NodeList messageNodes = validatorElement.getElementsByTagName("message"); + Element messageElement = (Element) messageNodes.item(0); + + final Node defaultMessageNode = messageElement.getFirstChild(); + String defaultMessage = (defaultMessageNode == null) ? "" : defaultMessageNode.getNodeValue(); + vCfg.defaultMessage(defaultMessage); + + Map messageParams = XmlHelper.getParams(messageElement); + String key = messageElement.getAttribute("key"); + + + if ((key != null) && (key.trim().length() > 0)) { + vCfg.messageKey(key); + + // Get the default message when pattern 2 is used. We are only interested in the + // i18n message parameters when an i18n message key is specified. + // pattern 1: + // Default message + // pattern 2: + // + // 'param1' + // 'param2' + // sortedMessageParameters = new TreeMap(); + for (Map.Entry messageParamEntry : messageParams.entrySet()) { + + try { + int _order = Integer.parseInt(messageParamEntry.getKey()); + sortedMessageParameters.put(Integer.valueOf(_order), messageParamEntry.getValue().toString()); + } + catch (NumberFormatException e) { + // ignore if its not numeric. + } + } + vCfg.messageParams(sortedMessageParameters.values().toArray(new String[sortedMessageParameters.values().size()])); + } else { + if (messageParams != null && (messageParams.size() > 0)) { + // we are i18n message parameters defined but no i18n message, + // let's warn the user. + LOG.warn("validator of type ["+validatorType+"] have i18n message parameters defined but no i18n message key, it's parameters will be ignored"); + } + } + + validatorCfgs.add(vCfg.build()); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java new file mode 100644 index 000000000..8fd62b6f6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java @@ -0,0 +1,310 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.*; + + +/** + * A default implementation of the {@link ValidatorContext} interface. + * + * @author Jason Carreira + * @author Rainer Hermanns + */ +public class DelegatingValidatorContext implements ValidatorContext { + + private LocaleProvider localeProvider; + private TextProvider textProvider; + private ValidationAware validationAware; + + /** + * Creates a new validation context given a ValidationAware object, and a text and locale provider. These objects + * are used internally to set errors and get and set error text. + */ + public DelegatingValidatorContext(ValidationAware validationAware, TextProvider textProvider, + LocaleProvider localeProvider) { + this.textProvider = textProvider; + this.validationAware = validationAware; + this.localeProvider = localeProvider; + } + + /** + * Creates a new validation context given an object - usually an Action. The internal objects + * (validation aware instance and a locale and text provider) are created based on the given action. + * + * @param object the object to use for validation (usually an Action). + */ + public DelegatingValidatorContext(Object object) { + this.localeProvider = makeLocaleProvider(object); + this.validationAware = makeValidationAware(object); + this.textProvider = makeTextProvider(object, localeProvider); + } + + /** + * Create a new validation context given a Class definition. The locale provider, text provider and + * the validation context are created based on the class. + * + * @param clazz the class to initialize the context with. + */ + public DelegatingValidatorContext(Class clazz) { + localeProvider = new ActionContextLocaleProvider(); + textProvider = new TextProviderFactory().createInstance(clazz, localeProvider); + validationAware = new LoggingValidationAware(clazz); + } + + 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(); + } + + public void setFieldErrors(Map> errorMap) { + validationAware.setFieldErrors(errorMap); + } + + public Map> getFieldErrors() { + return validationAware.getFieldErrors(); + } + + public String getFullFieldName(String fieldName) { + return fieldName; + } + + public Locale getLocale() { + return localeProvider.getLocale(); + } + + public boolean hasKey(String key) { + return textProvider.hasKey(key); + } + + public String getText(String aTextName) { + return textProvider.getText(aTextName); + } + + public String getText(String aTextName, String defaultValue) { + return textProvider.getText(aTextName, defaultValue); + } + + public String getText(String aTextName, String defaultValue, String obj) { + return textProvider.getText(aTextName, defaultValue, obj); + } + + public String getText(String aTextName, List args) { + return textProvider.getText(aTextName, args); + } + + public String getText(String key, String[] args) { + return textProvider.getText(key, args); + } + + public String getText(String aTextName, String defaultValue, List args) { + return textProvider.getText(aTextName, defaultValue, args); + } + + public String getText(String key, String defaultValue, String[] args) { + return textProvider.getText(key, defaultValue, args); + } + + public ResourceBundle getTexts(String aBundleName) { + return textProvider.getTexts(aBundleName); + } + + public String getText(String key, String defaultValue, List args, ValueStack stack) { + return textProvider.getText(key, defaultValue, args, stack); + } + + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + return textProvider.getText(key, defaultValue, args, stack); + } + + public ResourceBundle getTexts() { + return textProvider.getTexts(); + } + + 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 boolean hasActionErrors() { + return validationAware.hasActionErrors(); + } + + public boolean hasActionMessages() { + return validationAware.hasActionMessages(); + } + + public boolean hasErrors() { + return validationAware.hasErrors(); + } + + public boolean hasFieldErrors() { + return validationAware.hasFieldErrors(); + } + + public static TextProvider makeTextProvider(Object object, LocaleProvider localeProvider) { + // the object argument passed through here will most probably be an ActionSupport decendant which does + // implements TextProvider. + if ((object != null) && (object instanceof TextProvider)) { + return new CompositeTextProvider(new TextProvider[]{ + ((TextProvider) object), + new TextProviderSupport(object.getClass(), localeProvider) + }); + } else { + return new TextProviderFactory().createInstance(object.getClass(), localeProvider); + } + } + + protected static LocaleProvider makeLocaleProvider(Object object) { + if (object instanceof LocaleProvider) { + return (LocaleProvider) object; + } else { + return new ActionContextLocaleProvider(); + } + } + + protected static ValidationAware makeValidationAware(Object object) { + if (object instanceof ValidationAware) { + return (ValidationAware) object; + } else { + return new LoggingValidationAware(object); + } + } + + protected void setTextProvider(TextProvider textProvider) { + this.textProvider = textProvider; + } + + protected TextProvider getTextProvider() { + return textProvider; + } + + protected void setValidationAware(ValidationAware validationAware) { + this.validationAware = validationAware; + } + + protected ValidationAware getValidationAware() { + return validationAware; + } + + /** + * An implementation of LocaleProvider which gets the locale from the action context. + */ + private static class ActionContextLocaleProvider implements LocaleProvider { + public Locale getLocale() { + return ActionContext.getContext().getLocale(); + } + } + + /** + * An implementation of ValidationAware which logs errors and messages. + */ + private static class LoggingValidationAware implements ValidationAware { + + private Logger log; + + public LoggingValidationAware(Class clazz) { + log = LoggerFactory.getLogger(clazz); + } + + public LoggingValidationAware(Object obj) { + log = LoggerFactory.getLogger(obj.getClass()); + } + + public void setActionErrors(Collection errorMessages) { + for (Object errorMessage : errorMessages) { + String s = (String) errorMessage; + addActionError(s); + } + } + + public Collection getActionErrors() { + return null; + } + + public void setActionMessages(Collection messages) { + for (Object message : messages) { + String s = (String) message; + addActionMessage(s); + } + } + + public Collection getActionMessages() { + return null; + } + + public void setFieldErrors(Map> errorMap) { + for (Map.Entry> entry : errorMap.entrySet()) { + addFieldError(entry.getKey(), entry.getValue().toString()); + } + } + + public Map> getFieldErrors() { + return null; + } + + public void addActionError(String anErrorMessage) { + log.error("Validation error: " + anErrorMessage); + } + + public void addActionMessage(String aMessage) { + log.info("Validation Message: " + aMessage); + } + + public void addFieldError(String fieldName, String errorMessage) { + log.error("Validation error for " + fieldName + ":" + errorMessage); + } + + public boolean hasActionErrors() { + return false; + } + + public boolean hasActionMessages() { + return false; + } + + public boolean hasErrors() { + return false; + } + + public boolean hasFieldErrors() { + return false; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java new file mode 100644 index 000000000..307767145 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java @@ -0,0 +1,39 @@ +/* + * 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.validator; + +/** + * The FieldValidator interface defines the methods to be implemented by FieldValidators. + * Which are used by the XWork validation framework to validate Action properties before + * executing the Action. + */ +public interface FieldValidator extends Validator { + + /** + * Sets the field name to validate with this FieldValidator + * + * @param fieldName the field name + */ + void setFieldName(String fieldName); + + /** + * Gets the field name to be validated + * + * @return the field name + */ + String getFieldName(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java new file mode 100644 index 000000000..651270d97 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java @@ -0,0 +1,44 @@ +/* + * 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.validator; + + +/** + * This interface should be implemented by validators that can short-circuit the validator queue + * that it is in. + * + * @author Mark Woon + */ +public interface ShortCircuitableValidator { + + /** + * Sets whether this field validator should short circuit the validator queue + * it's in if validation fails. + * + * @param shortcircuit true if this field validator should short circuit on + * failure, false otherwise + */ + public void setShortCircuit(boolean shortcircuit); + + /** + * Gets whether this field validator should short circuit the validator queue + * it's in if validation fails. + * + * @return true if this field validator should short circuit on failure, + * false otherwise + */ + public boolean isShortCircuit(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidationException.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidationException.java new file mode 100644 index 000000000..584678a5b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidationException.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.validator; + + +/** + * ValidationException. + * + * @author Jason Carreira + */ +public class ValidationException extends Exception { + + /** + * Constructs an Exception with no specified detail message. + */ + public ValidationException() { + } + + /** + * Constructs an Exception with the specified detail message. + * + * @param s the detail message. + */ + public ValidationException(String s) { + super(s); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java new file mode 100644 index 000000000..7bf027721 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java @@ -0,0 +1,266 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.Validateable; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor; +import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; +import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + * + * + * This interceptor runs the action through the standard validation framework, which in turn checks the action against + * any validation rules (found in files such as ActionClass-validation.xml) and adds field-level and action-level + * error messages (provided that the action implements {@link com.opensymphony.xwork2.ValidationAware}). This interceptor + * is often one of the last (or second to last) interceptors applied in a stack, as it assumes that all values have + * already been set on the action. + * + *

This interceptor does nothing if the name of the method being invoked is specified in the excludeMethods + * parameter. excludeMethods accepts a comma-delimited list of method names. For example, requests to + * foo!input.action and foo!back.action will be skipped by this interceptor if you set the + * excludeMethods parameter to "input, back". + * + * + * + *

The workflow of the action request does not change due to this interceptor. Rather, + * this interceptor is often used in conjuction with the workflow interceptor. + * + *

+ * + * NOTE: As this method extends off MethodFilterInterceptor, it is capable of + * deciding if it is applicable only to selective methods in the action class. See + * MethodFilterInterceptor for more info. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • alwaysInvokeValidate - Defaults to true. If true validate() method will always + * be invoked, otherwise it will not.
  • + * + *
  • programmatic - Defaults to true. If true and the action is Validateable call validate(), + * and any method that starts with "validate". + *
  • + * + *
  • declarative - Defaults to true. Perform validation based on xml or annotations.
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * There are no known extension points for this interceptor. + * + * + * + *

Example code: + * + *

+ * 
+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="validation"/>
+ *     <interceptor-ref name="workflow"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * <-- in the following case myMethod of the action class will not
+ *        get validated -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="validation">
+ *         <param name="excludeMethods">myMethod</param>
+ *     </interceptor-ref>
+ *     <interceptor-ref name="workflow"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * <-- in the following case only annotated methods of the action class will
+ *        be validated -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="validation">
+ *         <param name="validateAnnotatedMethodOnly">true</param>
+ *     </interceptor-ref>
+ *     <interceptor-ref name="workflow"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ * 
+ * 
+ * + * @author Jason Carreira + * @author Rainer Hermanns + * @author Alexandru Popescu + * @see ActionValidatorManager + * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor + */ +public class ValidationInterceptor extends MethodFilterInterceptor { + + private boolean validateAnnotatedMethodOnly; + + private ActionValidatorManager actionValidatorManager; + + private static final Logger LOG = LoggerFactory.getLogger(ValidationInterceptor.class); + + private final static String VALIDATE_PREFIX = "validate"; + private final static String ALT_VALIDATE_PREFIX = "validateDo"; + + private boolean alwaysInvokeValidate = true; + private boolean programmatic = true; + private boolean declarative = true; + + @Inject + public void setActionValidatorManager(ActionValidatorManager mgr) { + this.actionValidatorManager = mgr; + } + + /** + * Determines if {@link Validateable}'s validate() should be called, + * as well as methods whose name that start with "validate". Defaults to "true". + * + * @param programmatic true then validate() is invoked. + */ + public void setProgrammatic(boolean programmatic) { + this.programmatic = programmatic; + } + + /** + * Determines if validation based on annotations or xml should be performed. Defaults + * to "true". + * + * @param declarative true then perform validation based on annotations or xml. + */ + public void setDeclarative(boolean declarative) { + this.declarative = declarative; + } + + /** + * Determines if {@link Validateable}'s validate() should always + * be invoked. Default to "true". + * + * @param alwaysInvokeValidate true then validate() is always invoked. + */ + public void setAlwaysInvokeValidate(String alwaysInvokeValidate) { + this.alwaysInvokeValidate = Boolean.parseBoolean(alwaysInvokeValidate); + } + + /** + * Gets if validate() should always be called or only per annotated method. + * + * @return true to only validate per annotated method, otherwise false to always validate. + */ + public boolean isValidateAnnotatedMethodOnly() { + return validateAnnotatedMethodOnly; + } + + /** + * Determine if validate() should always be called or only per annotated method. + * Default to false. + * + * @param validateAnnotatedMethodOnly true to only validate per annotated method, otherwise false to always validate. + */ + public void setValidateAnnotatedMethodOnly(boolean validateAnnotatedMethodOnly) { + this.validateAnnotatedMethodOnly = validateAnnotatedMethodOnly; + } + + /** + * Gets the current action and its context and delegates to {@link ActionValidatorManager} proper validate method. + * + * @param invocation the execution state of the Action. + * @throws Exception if an error occurs validating the action. + */ + protected void doBeforeInvocation(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + ActionProxy proxy = invocation.getProxy(); + + //the action name has to be from the url, otherwise validators that use aliases, like + //MyActio-someaction-validator.xml will not be found, see WW-3194 + String context = proxy.getActionName(); + String method = proxy.getMethod(); + + if (log.isDebugEnabled()) { + log.debug("Validating " + + invocation.getProxy().getNamespace() + "/" + invocation.getProxy().getActionName() + " with method "+ method +"."); + } + + + if (declarative) { + if (validateAnnotatedMethodOnly) { + actionValidatorManager.validate(action, context, method); + } else { + actionValidatorManager.validate(action, context); + } + } + + if (action instanceof Validateable && programmatic) { + // keep exception that might occured in validateXXX or validateDoXXX + Exception exception = null; + + Validateable validateable = (Validateable) action; + if (LOG.isDebugEnabled()) { + LOG.debug("Invoking validate() on action "+validateable); + } + + try { + PrefixMethodInvocationUtil.invokePrefixMethod( + invocation, + new String[] { VALIDATE_PREFIX, ALT_VALIDATE_PREFIX }); + } + catch(Exception e) { + // If any exception occurred while doing reflection, we want + // validate() to be executed + LOG.warn("an exception occured while executing the prefix method", e); + exception = e; + } + + + if (alwaysInvokeValidate) { + validateable.validate(); + } + + if (exception != null) { + // rethrow if something is wrong while doing validateXXX / validateDoXXX + throw exception; + } + } + } + + @Override + protected String doIntercept(ActionInvocation invocation) throws Exception { + doBeforeInvocation(invocation); + + return invocation.invoke(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/Validator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/Validator.java new file mode 100644 index 000000000..021a231c6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/Validator.java @@ -0,0 +1,490 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.util.ValueStack; + + +/** + * + *

The validators supplied by the XWork distribution (and any validators you + * might write yourself) come in two different flavors:

+ *

+ *

    + *
  1. Plain Validators / Non-Field validators
  2. + *
  3. FieldValidators
  4. + *
+ *

+ *

Plain Validators (such as the ExpressionValidator) perform validation checks + * that are not inherently tied to a single specified field. When you declare a + * plain Validator in your -validation.xml file you do not associate a fieldname + * attribute with it. (You should avoid using plain Validators within the + * syntax described below.)

+ *

+ *

FieldValidators (such as the EmailValidator) are designed to perform + * validation checks on a single field. They require that you specify a fieldname + * attribute in your -validation.xml file. There are two different (but equivalent) + * XML syntaxes you can use to declare FieldValidators (see " vs. + * syntax" below).

+ *

+ *

There are two places where the differences between the two validator flavors + * are important to keep in mind:

+ *

+ *

    + *
  1. when choosing the xml syntax used for declaring a validator + * (either or )
  2. + *
  3. when using the short-circuit capability
  4. + *
+ *

+ *

NOTE:Note that you do not declare what "flavor" of validator you are + * using in your -validation.xml file, you just declare the name of the validator + * to use and Struts will know whether it's a "plain Validator" or a "FieldValidator" + * by looking at the validation class that the validator's programmer chose + * to implement.

+ * + *

+ *

+ *

+ *

+ * + *

To define validation rules for an Action, create a file named ActionName-validation.xml + * in the same package as the Action. You may also create alias-specific validation rules which + * add to the default validation rules defined in ActionName-validation.xml by creating + * another file in the same directory named ActionName-aliasName-validation.xml. In both + * cases, ActionName is the name of the Action class, and aliasName is the name of the + * Action alias defined in the xwork.xml configuration for the Action.

+ *

+ *

The framework will also search up the inheritance tree of the Action to + * find validation rules for directly implemented interfaces and parent classes of the Action. + * This is particularly powerful when combined with ModelDriven Actions and the VisitorFieldValidator. + * Here's an example of how validation rules are discovered. Given the following class structure:

+ *

+ *

    + *
  • interface Animal;
  • + *
  • interface Quadraped extends Animal;
  • + *
  • class AnimalImpl implements Animal;
  • + *
  • class QuadrapedImpl extends AnimalImpl implements Quadraped;
  • + *
  • class Dog extends QuadrapedImpl;
  • + *
+ *

+ *

The framework method will look for the following config files if Dog is to be validated:

+ *

+ *

    + *
  • Animal
  • + *
  • Animal-aliasname
  • + *
  • AnimalImpl
  • + *
  • AnimalImpl-aliasname
  • + *
  • Quadraped
  • + *
  • Quadraped-aliasname
  • + *
  • QuadrapedImpl
  • + *
  • QuadrapedImpl-aliasname
  • + *
  • Dog
  • + *
  • Dog-aliasname
  • + *
+ *

+ *

While this process is similar to what the XW:Localization framework does + * when finding messages, there are some subtle differences. The most important + * difference is that validation rules are discovered from the parent downwards. + *

+ *

+ *

NOTE:Child's *-validation.xml will add on to parent's *-validation.xml + * according to the class hierarchy defined above. With this feature, one could have + * more generic validation rule at the parent and more specific validation rule at + * the child.

+ *

+ * + *

+ *

+ * + *

There are two ways you can define validators in your -validation.xml file:

+ *
    + *
  1. <validator>
  2. + *
  3. <field-validator>
  4. + *
+ *

Keep the following in mind when using either syntax:

+ *

+ *

Non-Field-Validator + * The <validator> element allows you to declare both types of validators + * (either a plain Validator a field-specific FieldValidator).

+ * + *

+ *

+ * 
+ *    <!-- Declaring a plain Validator using the <validator> syntax: -->
+ * 

+ * <validator type="expression> + * <param name="expression">foo gt bar</param> + * <message>foo must be great than bar.</message> + * </validator> + * + *

+ *

+ *

+ * 
+ *    <!-- Declaring a field validator using the <validator> syntax; -->
+ * 

+ * <validator type="required"> + * <param name="fieldName">bar</param> + * <message>You must enter a value for bar.</message> + * </validator> + * + *

+ *

+ *

+ * + *

field-validator + * The <field-validator> elements are basically the same as the <validator> elements + * except that they inherit the fieldName attribute from the enclosing <field> element. + * FieldValidators defined within a <field-validator> element will have their fieldName + * automatically filled with the value of the parent <field> element's fieldName + * attribute. The reason for this structure is to conveniently group the validators + * for a particular field under one element, otherwise the fieldName attribute + * would have to be repeated, over and over, for each individual <validator>.

+ *

+ *

HINT: + * It is always better to defined field-validator inside a <field> tag instead of + * using a <validator> tag and supplying fieldName as its param as the xml code itself + * is clearer (grouping of field is clearer)

+ *

+ *

NOTE: + * Note that you should only use FieldValidators (not plain Validators) within a + * block. A plain Validator inside a <field> will not be + * allowed and would generate error when parsing the xml, as it is not allowed in + * the defined dtd (xwork-validator-1.0.2.dtd)

+ * + *

+ *

+ * 
+ * Declaring a FieldValidator using the <field-validator> syntax:
+ * 

+ * <field name="email_address"> + * <field-validator type="required"> + * <message>You cannot leave the email address field empty.</message> + * </field-validator> + * <field-validator type="email"> + * <message>The email address you entered is not valid.</message> + * </field-validator> + * </field> + * + *

+ *

+ *

+ * + *

The choice is yours. It's perfectly legal to only use elements + * without the elements and set the fieldName attribute for each of them. + * The following are effectively equal:

+ * + *

+ *

+ * 
+ * <field name="email_address">
+ *   <field-validator type="required">
+ *       <message>You cannot leave the email address field empty.</message>
+ *   </field-validator>
+ *   <field-validator type="email">
+ *       <message>The email address you entered is not valid.</message>
+ *   </field-validator>
+ * </field>
+ * 

+ *

+ * <validator type="required"> + * <param name="fieldName">email_address</param> + * <message>You cannot leave the email address field empty.</message> + * </validator> + * <validator type="email"> + * <param name="fieldName">email_address</param> + * <message>The email address you entered is not valid.</message> + * </validator> + * + *

+ *

+ *

+ * + *

It is possible to short-circuit a stack of validators. + * Here is another sample config file containing validation rules from the + * Xwork test cases: Notice that some of the <field-validator> and + * <validator> elements have the short-circuit attribute set to true.

+ * + *

+ *

+ * <!-- START SNIPPET: exShortCircuitingValidators -->
+ * <!DOCTYPE validators PUBLIC
+ *         "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
+ *         "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+ * <validators>
+ *   <!-- Field Validators for email field -->
+ *   <field name="email">
+ *       <field-validator type="required" short-circuit="true">
+ *           <message>You must enter a value for email.</message>
+ *       </field-validator>
+ *       <field-validator type="email" short-circuit="true">
+ *           <message>Not a valid e-mail.</message>
+ *       </field-validator>
+ *   </field>
+ *   <!-- Field Validators for email2 field -->
+ *   <field name="email2">
+ *      <field-validator type="required">
+ *           <message>You must enter a value for email2.</message>
+ *       </field-validator>
+ *      <field-validator type="email">
+ *           <message>Not a valid e-mail2.</message>
+ *       </field-validator>
+ *   </field>
+ *   <!-- Plain Validator 1 -->
+ *   <validator type="expression">
+ *       <param name="expression">email.equals(email2)</param>
+ *       <message>Email not the same as email2</message>
+ *   </validator>
+ *   <!-- Plain Validator 2 -->
+ *   <validator type="expression" short-circuit="true">
+ *       <param name="expression">email.startsWith('mark')</param>
+ *       <message>Email does not start with mark</message>
+ *   </validator>
+ * </validators>
+ * <!-- END SNIPPET: exShortCircuitingValidators -->
+ * 
+ *

+ * + *

short-circuiting and Validator flavors

+ *

Plain validator takes precedence over field-validator. They get validated + * first in the order they are defined and then the field-validator in the order + * they are defined. Failure of a particular validator marked as short-circuit + * will prevent the evaluation of subsequent validators and an error (action + * error or field error depending on the type of validator) will be added to + * the ValidationContext of the object being validated.

+ *

+ *

In the example above, the actual execution of validator would be as follows:

+ *

+ *

    + *
  1. Plain Validator 1
  2. + *
  3. Plain Validator 2
  4. + *
  5. Field Validators for email field
  6. + *
  7. Field Validators for email2 field
  8. + *
+ *

+ *

Since Plain Validator 2 is short-circuited, if its validation failed, + * it will causes Field validators for email field and Field validators for email2 + * field to not be validated as well.

+ *

+ *

Usefull Information: + * More complicated validation should probably be done in the validate() + * method on the action itself (assuming the action implements Validatable + * interface which ActionSupport already does).

+ *

+ *

+ * A plain Validator (non FieldValidator) that gets short-circuited will + * completely break out of the validation stack. No other validators will be + * evaluated and plain validators takes precedence over field validators meaning + * that they get evaluated in the order they are defined before field validators + * get a chance to be evaluated. + *

+ * + *

+ *

+ * + *

Short cuircuiting and validator flavours

+ *

A FieldValidator that gets short-circuited will only prevent other + * FieldValidators for the same field from being evaluated. Note that this + * "same field" behavior applies regardless of whether the or + * syntax was used to declare the validation rule. + * By way of example, given this -validation.xml file:

+ * + *

+ *

+ * 
+ * <validator type="required" short-circuit="true">
+ *   <param name="fieldName">bar</param>
+ *   <message>You must enter a value for bar.</message>
+ * </validator>
+ * 

+ * <validator type="expression"> + * <param name="expression">foo gt bar</param> + * <message>foo must be great than bar.</message> + * </validator> + * + *

+ *

+ * + *

both validators will be run, even if the "required" validator short-circuits. + * "required" validators are FieldValidator's and will not short-circuit the plain + * ExpressionValidator because FieldValidators only short-circuit other checks on + * that same field. Since the plain Validator is not field specific, it is + * not short-circuited.

+ * + *

+ *

+ * + *

As mentioned above, the framework will also search up the inheritance tree + * of the action to find default validations for interfaces and parent classes of + * the Action. If you are using the short-circuit attribute and relying on + * default validators higher up in the inheritance tree, make sure you don't + * accidentally short-circuit things higher in the tree that you really want!

+ *

+ * The effect of having common validators on both + *

+ *
    + *
  • <actionClass>-validation.xml
  • + *
  • <actionClass>-<actionAlias>-validation.xml
  • + *
+ *

+ * It should be noted that the nett effect will be validation on both the validators available + * in both validation configuration file. For example if we have 'requiredstring' validators defined + * in both validation xml file for field named 'address', we will see 2 validation error indicating that + * the the address cannot be empty (assuming validation failed). This is due to WebWork + * will merge validators found in both validation configuration files. + *

+ *

+ * The logic behind this design decision is such that we could have common validators in + * <actionClass>-validation.xml and more context specific validators to be located + * in <actionClass>-<actionAlias>-validation.xml + *

+ * + * + *

+ * + * Validator's validation messages could be internatinalized. For example, + *

+ *   <field-validator type="required">
+ *      <message key="required.field" />
+ *   </field-validator>
+ * 
+ * or + *
+ *   <validator type="expression">
+ *      <param name="expression">email.startsWith('Mark')</param>
+ *      <message key="email.invalid" />
+ *   </validator>
+ * 
+ * In the first case, WebWork would look for i18n with key 'required.field' as the validation error message if + * validation fails, and 'email.invalid' in the second case. + *

+ * We could also provide a default message such that if validation failed and the i18n key for the message + * cannot be found, WebWork would fall back and use the default message. An example would be as follows :- + *

+ *   <field-validator type="required">
+ *      <message key="required.field">This field is required.</message>
+ *   </field-validator>
+ * 
+ * or + *
+ *   <validator type="expression">
+ *      <param name="expression">email.startsWith('Mark')</param>
+ *      <message key="email.invalid">Email needs with starts with Mark</message>
+ *   </validator>
+ * 
+ * + * + * + * @author Jason Carreira + */ +public interface Validator { + + /** + * Sets the default message to use for validation failure + * + * @param message the default message + */ + void setDefaultMessage(String message); + + /** + * Gets the default message used for validation failures + * + * @return the default message + */ + String getDefaultMessage(); + + /** + * Gets the validation failure message for the given object + * + * @param object object being validated (eg. a domain model object) + * @return the validation failure message + */ + String getMessage(Object object); + + /** + * Sets a resource bundle key to be used for lookup of validation failure message + * + * @param key the resource bundle key + */ + void setMessageKey(String key); + + /** + * Gets the resource bundle key used for lookup of validation failure message + * + * @return the resource bundle key + */ + String getMessageKey(); + + /** + * Sets the messsage parameters to be used when parsing i18n messages + * + * @param messageParameters the messsage parameters + */ + void setMessageParameters(String[] messageParameters); + + /** + * Gets the messsage parameters to be used when parsing i18n messages + * + * @return the messsage parameters + */ + String[] getMessageParameters(); + + /** + * This method will be called before validate with a non-null ValidatorContext. + * + * @param validatorContext the validation context to use. + */ + void setValidatorContext(ValidatorContext validatorContext); + + /** + * Gets the validation context used + * + * @return the validation context + */ + ValidatorContext getValidatorContext(); + + /** + * The validation implementation must guarantee that setValidatorContext will + * be called with a non-null ValidatorContext before validate is called. + * + * @param object the object to be validated. + * @throws ValidationException is thrown if there is validation error(s). + */ + void validate(Object object) throws ValidationException; + + /** + * Sets the validator type to use (see class javadoc). + * + * @param type the type to use. + */ + void setValidatorType(String type); + + /** + * Gets the vaildator type used (see class javadoc). + * + * @return the type used + */ + String getValidatorType(); + + /** + * Sets the value stack to use to resolve values and parameters + * + * @param stack The value stack for the request + * @since 2.1.1 + */ + void setValueStack(ValueStack stack); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorConfig.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorConfig.java new file mode 100644 index 000000000..b55af0d02 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorConfig.java @@ -0,0 +1,170 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Holds the necessary information for configuring an instance of a Validator. + * + * + * @author James House + * @author Rainer Hermanns + * @author tm_jee + * @author Martin Gilday + */ +public class ValidatorConfig extends Located { + + private String type; + private Map params; + private String defaultMessage; + private String messageKey; + private boolean shortCircuit; + private String[] messageParams; + + /** + * @param validatorType + */ + protected ValidatorConfig(String validatorType) { + this.type = validatorType; + params = new LinkedHashMap(); + } + + protected ValidatorConfig(ValidatorConfig orig) { + this.type = orig.type; + this.params = new LinkedHashMap(orig.params); + this.defaultMessage = orig.defaultMessage; + this.messageKey = orig.messageKey; + this.shortCircuit = orig.shortCircuit; + this.messageParams = orig.messageParams; + } + + /** + * @return Returns the defaultMessage for the validator. + */ + public String getDefaultMessage() { + return defaultMessage; + } + + /** + * @return Returns the messageKey for the validator. + */ + public String getMessageKey() { + return messageKey; + } + + /** + * @return Returns wether the shortCircuit flag should be set on the + * validator. + */ + public boolean isShortCircuit() { + return shortCircuit; + } + + /** + * @return Returns the configured params to set on the validator. + */ + public Map getParams() { + return params; + } + + /** + * @return Returns the type of validator to configure. + */ + public String getType() { + return type; + } + + /** + * @return The i18n message parameters/arguments to be used. + */ + public String[] getMessageParams() { + return messageParams; + } + + /** + * Builds a ValidatorConfig + */ + public static final class Builder { + private ValidatorConfig target; + + public Builder(String validatorType) { + target = new ValidatorConfig(validatorType); + } + + public Builder(ValidatorConfig config) { + target = new ValidatorConfig(config); + } + + public Builder shortCircuit(boolean shortCircuit) { + target.shortCircuit = shortCircuit; + return this; + } + + public Builder defaultMessage(String msg) { + if ((msg != null) && (msg.trim().length() > 0)) { + target.defaultMessage = msg; + } + return this; + } + + public Builder messageParams(String[] msgParams) { + target.messageParams = msgParams; + return this; + } + + public Builder messageKey(String key) { + if ((key != null) && (key.trim().length() > 0)) { + target.messageKey = key; + } + return this; + } + + public Builder addParam(String name, String value) { + if (value != null && name != null) { + 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 ValidatorConfig build() { + target.params = Collections.unmodifiableMap(target.params); + ValidatorConfig result = target; + target = new ValidatorConfig(target); + return result; + } + + public Builder removeParam(String key) { + target.params.remove(key); + return this; + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorContext.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorContext.java new file mode 100644 index 000000000..f79586724 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorContext.java @@ -0,0 +1,38 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.LocaleProvider; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.ValidationAware; + + +/** + * The context for validation. This interface extends others to provide methods for reporting + * errors and messages as well as looking up error messages in a resource bundle using a specific locale. + * + * @author Jason Carreira + */ +public interface ValidatorContext extends ValidationAware, TextProvider, LocaleProvider { + + /** + * Translates a simple field name into a full field name in OGNL syntax. + * + * @param fieldName the field name to lookup. + * @return the full field name in OGNL syntax. + */ + String getFullFieldName(String fieldName); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorFactory.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorFactory.java new file mode 100644 index 000000000..92fec21bf --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorFactory.java @@ -0,0 +1,239 @@ +package com.opensymphony.xwork2.validator; + +/** + * ValidatorFactory + * + *

+ * + * Validation rules are handled by validators, which must be registered with + * the ValidatorFactory (using the registerValidator method). The simplest way to do so is to add a file name + * validators.xml in the root of the classpath (/WEB-INF/classes) that declares + * all the validators you intend to use. + * + *

+ * + * + *

+ * INFORMATION + * + * validators.xml if being defined should be available in the classpath. However + * this is not necessary, if no custom validator is needed. Predefined sets of validators + * will automatically be picked up when defined in + * com/opensymphony/xwork2/validator/validators/default.xml packaged in + * in the xwork jar file. See ValidatorFactory static block for details. + * + *

+ * + *

+ * WARNING + * + * If custom validator is being defined and a validators.xml is created and + * place in the classpath, do remember to copy all the other pre-defined validators + * that is needed into the validators.xml as if not they will not be registered. + * Once a validators.xml is detected in the classpath, the default one + * (com/opensymphony/xwork2/validator/validators/default.xml) will not be loaded. + * It is only loaded when a custom validators.xml cannot be found in the classpath. + * Be careful. + * + *

+ * + *

Note: + * + * The default validationWorkflowStack already includes this.
+ * All that is required to enable validation for an Action is to put the + * ValidationInterceptor in the interceptor refs of the action (see xwork.xml) like so: + * + *

+ * + *
+ * 
+ *     <interceptor name="validator" class="com.opensymphony.xwork2.validator.ValidationInterceptor"/>
+ * 
+ * 
+ * + *

Field Validators + * + * Field validators, as the name indicate, act on single fields accessible through an action. + * A validator, in contrast, is more generic and can do validations in the full action context, + * involving more than one field (or even no field at all) in validation rule. + * Most validations can be defined on per field basis. This should be preferred over + * non-field validation wherever possible, as field validator messages are bound to the + * related field and will be presented next to the corresponding input element in the + * respecting view. + * + *

+ * + *

Non Field Validators + * + * Non-field validators only add action level messages. Non-field validators + * are mostly domain specific and therefore offer custom implementations. + * The most important standard non-field validator provided by XWork + * is ExpressionValidator. + * + *

+ * + *

NOTE: + * + * Non-field validators takes precedence over field validators + * regardless of the order they are defined in *-validation.xml. If a non-field + * validator is short-circuited, it will causes its non-field validator to not + * being executed. See validation framework documentation for more info. + * + *

+ * + *

VALIDATION RULES: + * + * Validation rules can be specified: + *

    + *
  1. Per Action class: in a file named ActionName-validation.xml
  2. + *
  3. Per Action alias: in a file named ActionName-alias-validation.xml
  4. + *
  5. Inheritance hierarchy and interfaces implemented by Action class: + * XWork searches up the inheritance tree of the action to find default + * validations for parent classes of the Action and interfaces implemented
  6. + *
+ * Here is an example for SimpleAction-validation.xml: + * + *

+ * + *

+ * 
+ * <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
+ *        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+ * <validators>
+ *   <field name="bar">
+ *       <field-validator type="required">
+ *           <message>You must enter a value for bar.</message>
+ *       </field-validator>
+ *       <field-validator type="int">
+ *           <param name="min">6</param>
+ *           <param name="max">10</param>
+ *           <message>bar must be between ${min} and ${max}, current value is ${bar}.</message>
+ *       </field-validator>
+ *   </field>
+ *   <field name="bar2">
+ *       <field-validator type="regex">
+ *           <param name="expression">[0-9],[0-9]</param>
+ *           <message>The value of bar2 must be in the format "x, y", where x and y are between 0 and 9</message>
+ *      </field-validator>
+ *   </field>
+ *   <field name="date">
+ *       <field-validator type="date">
+ *           <param name="min">12/22/2002</param>
+ *           <param name="max">12/25/2002</param>
+ *           <message>The date must be between 12-22-2002 and 12-25-2002.</message>
+ *       </field-validator>
+ *   </field>
+ *   <field name="foo">
+ *       <field-validator type="int">
+ *           <param name="min">0</param>
+ *           <param name="max">100</param>
+ *           <message key="foo.range">Could not find foo.range!</message>
+ *       </field-validator>
+ *   </field>
+ *   <validator type="expression">
+ *       <param name="expression">foo lt bar </param>
+ *       <message>Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}.</message>
+ *   </validator>
+ * </validators>
+ * 
+ * 
+ * + * + *

+ * + * Here we can see the configuration of validators for the SimpleAction class. + * Validators (and field-validators) must have a type attribute, which refers + * to a name of an Validator registered with the ValidatorFactory as above. + * Validator elements may also have <param> elements with name and value attributes + * to set arbitrary parameters into the Validator instance. See below for discussion + * of the message element. + * + *

+ * + * + * + * + *

Each Validator or Field-Validator element must define one message element inside + * the validator element body. The message element has 1 attributes, key which is not + * required. The body of the message tag is taken as the default message which should + * be added to the Action if the validator fails. Key gives a message key to look up + * in the Action's ResourceBundles using getText() from LocaleAware if the Action + * implements that interface (as ActionSupport does). This provides for Localized + * messages based on the Locale of the user making the request (or whatever Locale + * you've set into the LocaleAware Action). After either retrieving the message from + * the ResourceBundle using the Key value, or using the Default message, the current + * Validator is pushed onto the ValueStack, then the message is parsed for \$\{...\} + * sections which are replaced with the evaluated value of the string between the + * \$\{ and \}. This allows you to parameterize your messages with values from the + * Validator, the Action, or both.

+ * + * + *

If the validator fails, the validator is pushed onto the ValueStack and the + * message - either the default or the locale-specific one if the key attribute is + * defined (and such a message exists) - is parsed for ${...} sections which are + * replaced with the evaluated value of the string between the ${ and }. This + * allows you to parameterize your messages with values from the validator, the + * Action, or both.

+ * + *

NOTE: Since validation rules are in an XML file, you must make sure + * you escape special characters. For example, notice that in the expression + * validator rule above we use "&gt;" instead of ">". Consult a resource on XML + * for the full list of characters that must be escaped. The most commonly used + * characters that must be escaped are: & (use &amp;), > (user &gt;), and < (use &lt;).

+ * + *

Here is an example of a parameterized message:

+ *

This will pull the min and max parameters from the IntRangeFieldValidator and + * the value of bar from the Action.

+ * + * + *
+ * 
+ *    bar must be between ${min} and ${max}, current value is ${bar}.
+ * 
+ * 
+ * + * + *

Another notable fact is that the provided message value is capable of containing OGNL expressions. + * Keeping this in mind, it is possible to construct quite sophisticated messages.

+ *

See the following example to get an impression:

+ * + * + * + *
+ * 
+ *    ${getText("validation.failednotice")}! ${getText("reason")}: ${getText("validation.inputrequired")}
+ * 
+ * 
+ * + * @version $Date$ $Id$ + * @author Jason Carreira + * @author James House + */ +public interface ValidatorFactory { + + /** + * Get a Validator that matches the given configuration. + * + * @param cfg the configurator. + * @return the validator. + */ + Validator getValidator(ValidatorConfig cfg); + + /** + * Registers the given validator to the existing map of validators. + * This will add to the existing list. + * + * @param name name of validator to add. + * @param className the FQ classname of the validator. + */ + void registerValidator(String name, String className); + + /** + * Lookup to get the FQ classname of the given validator name. + * + * @param name name of validator to lookup. + * @return the found FQ classname + * @throws IllegalArgumentException is thrown if the name is not found. + */ + String lookupRegisteredValidatorType(String name); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorFileParser.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorFileParser.java new file mode 100644 index 000000000..2c9e39d22 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/ValidatorFileParser.java @@ -0,0 +1,46 @@ +package com.opensymphony.xwork2.validator; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +/** + * This class serves 2 purpose : + *
    + *
  • + * Parse the validation config file. (eg. MyAction-validation.xml, MyAction-actionAlias-validation.xml) + * to return a List of ValidatorConfig encapsulating the validator information. + *
  • + *
  • + * Parse the validator definition file, (eg. validators.xml) that defines the {@link Validator}s + * registered with XWork. + *
  • + *
+ * + * @author Jason Carreira + * @author James House + * @author tm_jee ( tm_jee (at) yahoo.co.uk ) + * @author Rob Harrop + * @author Rene Gielen + * + * @see com.opensymphony.xwork2.validator.ValidatorConfig + */ +public interface ValidatorFileParser { + /** + * Parse resource for a list of ValidatorConfig objects (configuring which validator(s) are + * being applied to a particular field etc.) + * + * @param is input stream to the resource + * @param resourceName file name of the resource + * @return List list of ValidatorConfig + */ + List parseActionValidatorConfigs(ValidatorFactory validatorFactory, InputStream is, String resourceName); + + /** + * Parses validator definitions (register various validators with XWork). + * + * @param is The input stream + * @param resourceName The location of the input stream + */ + void parseValidatorDefinitions(Map validators, InputStream is, String resourceName); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ConditionalVisitorFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ConditionalVisitorFieldValidator.java new file mode 100644 index 000000000..63408280a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ConditionalVisitorFieldValidator.java @@ -0,0 +1,145 @@ +package com.opensymphony.xwork2.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * The validator allows you to forward validator to object properties of your action + * using the objects own validator files. This allows you to use the ModelDriven development + * pattern and manage your validations for your models in one place, where they belong, next to + * your model classes. + * + * The ConditionalVisitorFieldValidator can handle either simple Object properties, Collections of Objects, or Arrays. + * The error message for the ConditionalVisitorFieldValidator will be appended in front of validator messages added + * by the validations for the Object message. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
expressionyes Boolean conditional expression
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
context no action alias Determines the context to use for validating the Object property. If not defined, the context of the Action validation is propogated to the Object property validation. In the case of Action validation, this context is the Action alias.
appendPrefix no true Determines whether the field name of this field validator should be prepended to the field name of the visited field to determine the full field name when an error occurs. For example, suppose that the bean being validated has a "name" property. If appendPrefix is true, then the field error will be stored under the field "bean.name". If appendPrefix is false, then the field error will be stored under the field "name".
If you are using the VisitorFieldValidator to validate the model from a ModelDriven Action, you should set appendPrefix to false unless you are using "model.name" to reference the properties on your model.
+ * + * + *

Example code: + * + *

+ * 
+ * @ConditionalVisitorFieldValidator(expression="app.appid > 100",  message = "Default message", key = "i18n.key", shortCircuit = true, context = "action alias", appendPrefix = true)
+ * 
+ * 
+ * + * @author Matt Raible + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConditionalVisitorFieldValidator { + + /** + * Determines the context to use for validating the Object property. + * If not defined, the context of the Action validator is propogated to the Object property validator. + * In the case of Action validator, this context is the Action alias. + */ + String context() default ""; + + /** + * Determines whether the field name of this field validator should be prepended to the field name of + * the visited field to determine the full field name when an error occurs. For example, suppose that + * the bean being validated has a "name" property. + * + * If appendPrefix is true, then the field error will be stored under the field "bean.name". + * If appendPrefix is false, then the field error will be stored under the field "name". + * + * If you are using the ConditionalVisitorFieldValidator to validate the model from a ModelDriven Action, + * you should set appendPrefix to false unless you are using "model.name" to reference the properties + * on your model. + */ + boolean appendPrefix() default true; + + /** + * The conditional expression. + */ + String expression(); + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ConversionErrorFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ConversionErrorFieldValidator.java new file mode 100644 index 000000000..e79401954 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ConversionErrorFieldValidator.java @@ -0,0 +1,123 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks if there are any conversion errors for a field and applies them if they exist. + * See Type Conversion Error Handling for details. + * + * + *

Annotation usage: + * + * + *

The ConversionErrorFieldValidator annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
+ * + * + *

Example code: + * + *

+ * 
+ * @ConversionErrorFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true)
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConversionErrorFieldValidator { + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/CustomValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/CustomValidator.java new file mode 100644 index 000000000..240a2d92a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/CustomValidator.java @@ -0,0 +1,113 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This annotation can be used for custom validators. Use the ValidationParameter annotation to supply additional params. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method or type level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
+ * + * + *

Example code: + * + *

+ * 
+ * @CustomValidator(type ="customValidatorName", fieldName = "myField")
+ * 
+ * 
+ * + * @author jepjep + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CustomValidator { + + String type(); + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + String key() default ""; + + public ValidationParameter[] parameters() default {}; + + boolean shortCircuit() default false; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/DateRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/DateRangeFieldValidator.java new file mode 100644 index 000000000..fd47fce8d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/DateRangeFieldValidator.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a date field has a value within a specified range. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
min no   Date property. The minimum the date must be.
max no   Date property. The maximum date can be.
+ * + *

If neither min nor max is set, nothing will be done.

+ * + * + *

Example code: + * + *

+ * 
+ * @DateRangeFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true, min = "2005/01/01", max = "2005/12/31")
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateRangeFieldValidator { + + /** + * Date property. The minimum the date must be. + */ + String min() default ""; + + /** + * Date property. The maximum date can be. + */ + String max() default ""; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/DoubleRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/DoubleRangeFieldValidator.java new file mode 100644 index 000000000..50f4e6164 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/DoubleRangeFieldValidator.java @@ -0,0 +1,170 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a double field has a value within a specified range. + * If neither min nor max is set, nothing will be done. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
minInclusive no   Double property. The inclusive minimum the number must be.
maxInclusive no   Double property. The inclusive maximum number can be.
minExclusive no   Double property. The exclusive minimum the number must be.
maxExclusive no   Double property. The exclusive maximum number can be.
+ * + *

If neither min nor max is set, nothing will be done.

+ * + *

The values for min and max must be inserted as String values so that "0" can be handled as a possible value.

+ * + * + *

Example code: + * + *

+ * 
+ * @DoubleRangeFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true, minInclusive = "0.123", maxInclusive = "99.987")
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DoubleRangeFieldValidator { + + /** + * Double property. The inclusive minimum the number must be. + */ + String minInclusive() default ""; + + /** + * Double property. The inclusive minimum the number must be. + */ + String maxInclusive() default ""; + + /** + * Double property. The exclusive maximum number can be. + */ + String minExclusive() default ""; + + /** + * Double property. The exclusive maximum number can be. + */ + String maxExclusive() default ""; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/EmailValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/EmailValidator.java new file mode 100644 index 000000000..7ee9515dd --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/EmailValidator.java @@ -0,0 +1,122 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a field is a valid e-mail address if it contains a non-empty String. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
+ * + * + *

Example code: + * + *

+ * 
+ * @EmailValidator(message = "Default message", key = "i18n.key", shortCircuit = true)
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EmailValidator { + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ExpressionValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ExpressionValidator.java new file mode 100644 index 000000000..52768462e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ExpressionValidator.java @@ -0,0 +1,112 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This non-field level validator validates a supplied regular expression. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
shortCircuitnofalseIf this validator should be used as shortCircuit.
expression yes   An OGNL expression that returns a boolean value.
+ * + * + *

Example code: + * + *

+ * 
+ * @ExpressionValidator(message = "Default message", key = "i18n.key", shortCircuit = true, expression = "an OGNL expression" )
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD}) +public @interface ExpressionValidator { + + /** + * The expressions to validate. + * An OGNL expression that returns a boolean value. + */ + String expression(); + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/FieldExpressionValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/FieldExpressionValidator.java new file mode 100644 index 000000000..854ef8899 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/FieldExpressionValidator.java @@ -0,0 +1,123 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator uses an OGNL expression to perform its validator. + * The error message will be added to the field if the expression returns + * false when it is evaluated against the value stack. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
expression yes   An OGNL expression that returns a boolean value.
+ * + * + *

Example code: + * + *

+ * 
+ * @FieldExpressionValidator(message = "Default message", key = "i18n.key", shortCircuit = true, expression = "an OGNL expression")
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FieldExpressionValidator { + + /** + * An OGNL expression that returns a boolean value. + */ + String expression(); + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/IntRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/IntRangeFieldValidator.java new file mode 100644 index 000000000..439701716 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/IntRangeFieldValidator.java @@ -0,0 +1,149 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a numeric field has a value within a specified range. + * If neither min nor max is set, nothing will be done. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
min no   Integer property. The minimum the number must be.
max no   Integer property. The maximum number can be.
+ * + *

If neither min nor max is set, nothing will be done.

+ * + *

The values for min and max must be inserted as String values so that "0" can be handled as a possible value.

+ * + * + *

Example code: + * + *

+ * 
+ * @IntRangeFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true, min = "0", max = "42")
+ * 
+ * 
+ * + * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IntRangeFieldValidator { + + /** + * Integer property. The minimum the number must be. + */ + String min() default ""; + + /** + * Integer property. The maximum number can be. + */ + String max() default ""; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RegexFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RegexFieldValidator.java new file mode 100644 index 000000000..77e3219a2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RegexFieldValidator.java @@ -0,0 +1,130 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * Validates a string field using a regular expression. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
expressionyes The regex to validate the field value against.
+ * + * + *

Example code: + * + *

+ * 
+ * @RegexFieldValidator( key = "regex.field", expression = "yourregexp")
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RegexFieldValidator { + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + + String expression(); + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RequiredFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RequiredFieldValidator.java new file mode 100644 index 000000000..7ff4a96c1 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RequiredFieldValidator.java @@ -0,0 +1,123 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a field is non-null. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
+ * + * + *

Example code: + * + *

+ * 
+ * @RequiredFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true)
+ * 
+ * 
+ * + * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequiredFieldValidator { + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RequiredStringValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RequiredStringValidator.java new file mode 100644 index 000000000..bfdad5d3d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/RequiredStringValidator.java @@ -0,0 +1,134 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a String field is not empty (i.e. non-null with a length > 0). + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
trim no true Boolean property. Determines whether the String is trimmed before performing the length check.
+ * + * + *

Example code: + * + *

+ * 
+ * @RequiredStringValidator(message = "Default message", key = "i18n.key", shortCircuit = true, trim = true)
+ * 
+ * 
+ * + * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequiredStringValidator { + + /** + * Boolean property. Determines whether the String is trimmed before performing the length check. + */ + boolean trim() default true; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/StringLengthFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/StringLengthFieldValidator.java new file mode 100644 index 000000000..ce4be991c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/StringLengthFieldValidator.java @@ -0,0 +1,159 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a String field is of the right length. It assumes that the field is a String. + * If neither minLength nor maxLength is set, nothing will be done. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
trim no true Boolean property. Determines whether the String is trimmed before performing the length check.
minLength no   Integer property. The minimum length the String must be.
maxLength no   Integer property. The maximum length the String can be.
+ * + *

If neither minLength nor maxLength is set, nothing will be done.

+ * + * + * + *

Example code: + * + *

+ * 
+ * @StringLengthFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true, trim = true, minLength = "5",  maxLength = "12")
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface StringLengthFieldValidator { + + /** + * Boolean property. Determines whether the String is trimmed before performing the length check. + */ + boolean trim() default true; + + /** + * Integer property. The minimum length the String must be. + */ + String minLength() default ""; + + /** + * Integer property. The maximum length the String can be. + */ + String maxLength() default ""; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType[] type() default {ValidatorType.FIELD}; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/UrlValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/UrlValidator.java new file mode 100644 index 000000000..4a9b4a52a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/UrlValidator.java @@ -0,0 +1,122 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This validator checks that a field is a valid URL. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
typeyesValidatorType.FIELDEnum value from ValidatorType. Either FIELD or SIMPLE can be used here.
+ * + * + *

Example code: + * + *

+ * 
+ * @UrlValidator(message = "Default message", key = "i18n.key", shortCircuit = true)
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface UrlValidator { + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; + + /** + * The validation type for this field/method. + */ + ValidatorType type() default ValidatorType.FIELD; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/Validation.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/Validation.java new file mode 100644 index 000000000..2769b12c5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/Validation.java @@ -0,0 +1,137 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * This annotation has been deprecated since 2.1 as its previous purpose, to define classes that support annotation validations, + * is no longer necessary. + * + * + *

Annotation usage: + * + * + *

The Validation annotation must be applied at Type level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
validationsyes 
+ * + * + *

Example code: + * + * An Annotated Interface + *

+ * 
+ * @Validation()
+ * public interface AnnotationDataAware {
+ *
+ *     void setBarObj(Bar b);
+ *
+ *     Bar getBarObj();
+ *
+ *     @RequiredFieldValidator(message = "You must enter a value for data.")
+ *     @RequiredStringValidator(message = "You must enter a value for data.")
+ *     void setData(String data);
+ *
+ *     String getData();
+ * }
+ * 
+ * 
+ * + *

Example code: + * + * An Annotated Class + *

+ * 
+ * @Validation()
+ * public class SimpleAnnotationAction extends ActionSupport {
+ *
+ *     @RequiredFieldValidator(type = ValidatorType.FIELD, message = "You must enter a value for bar.")
+ *     @IntRangeFieldValidator(type = ValidatorType.FIELD, min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.")
+ *     public void setBar(int bar) {
+ *         this.bar = bar;
+ *     }
+ *
+ *     public int getBar() {
+ *         return bar;
+ *     }
+ *
+ *     @Validations(
+ *             requiredFields =
+ *                     {@RequiredFieldValidator(type = ValidatorType.SIMPLE, fieldName = "customfield", message = "You must enter a value for field.")},
+ *             requiredStrings =
+ *                     {@RequiredStringValidator(type = ValidatorType.SIMPLE, fieldName = "stringisrequired", message = "You must enter a value for string.")},
+ *             emails =
+ *                     { @EmailValidator(type = ValidatorType.SIMPLE, fieldName = "emailaddress", message = "You must enter a value for email.")},
+ *             urls =
+ *                     { @UrlValidator(type = ValidatorType.SIMPLE, fieldName = "hreflocation", message = "You must enter a value for email.")},
+ *             stringLengthFields =
+ *                     {@StringLengthFieldValidator(type = ValidatorType.SIMPLE, trim = true, minLength="10" , maxLength = "12", fieldName = "needstringlength", message = "You must enter a stringlength.")},
+ *             intRangeFields =
+ *                     { @IntRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "intfield", min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.")},
+ *             dateRangeFields =
+ *                     {@DateRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "datefield", min = "-1", max = "99", message = "bar must be between ${min} and ${max}, current value is ${bar}.")},
+ *             expressions = {
+ *                 @ExpressionValidator(expression = "foo > 1", message = "Foo must be greater than Bar 1. Foo = ${foo}, Bar = ${bar}."),
+ *                 @ExpressionValidator(expression = "foo > 2", message = "Foo must be greater than Bar 2. Foo = ${foo}, Bar = ${bar}."),
+ *                 @ExpressionValidator(expression = "foo > 3", message = "Foo must be greater than Bar 3. Foo = ${foo}, Bar = ${bar}."),
+ *                 @ExpressionValidator(expression = "foo > 4", message = "Foo must be greater than Bar 4. Foo = ${foo}, Bar = ${bar}."),
+ *                 @ExpressionValidator(expression = "foo > 5", message = "Foo must be greater than Bar 5. Foo = ${foo}, Bar = ${bar}.")
+ *     }
+ *     )
+ *     public String execute() throws Exception {
+ *         return SUCCESS;
+ *     }
+ * }
+ *
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @deprecated Since Struts 2.1 because it isn't necessary anymore + * @version $Id$ + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Validation { + + /** + * Used for class or interface validation rules. + */ + Validations[] validations() default {}; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ValidationParameter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ValidationParameter.java new file mode 100644 index 000000000..1482aed55 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ValidationParameter.java @@ -0,0 +1,83 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * The ValidationParameter annotation is used as a parameter for CustomValidators. + * + * + *

Annotation usage: + * + * + *

The annotation must embedded into CustomValidator annotations as a parameter. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
nameyes parameter name.
valueyes parameter value.
+ * + * + *

Example code: + * + *

+ * 
+ * @CustomValidator(
+ *   type ="customValidatorName",
+ *   fieldName = "myField",
+ *   parameters = { @ValidationParameter( name = "paramName", value = "paramValue" ) }
+ * )
+ * 
+ * 
+ * + * @author jepjep + * @author Rainer Hermanns + */ +@Target( { ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidationParameter { + + String name(); + + String value(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/Validations.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/Validations.java new file mode 100644 index 000000000..b33f6d223 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/Validations.java @@ -0,0 +1,186 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + *

If you want to use several annotations of the same type, these annotations must be nested within the @Validations() annotation.

+ * + * + *

Annotation usage: + * + * + *

Used at METHOD level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Notes
requiredFields no Add list of RequiredFieldValidators
customValidators no Add list of CustomValidators
conversionErrorFields no Add list of ConversionErrorFieldValidators
dateRangeFields no Add list of DateRangeFieldValidators
emails no Add list of EmailValidators
fieldExpressions no Add list of FieldExpressionValidators
intRangeFields no Add list of IntRangeFieldValidators
requiredStrings no Add list of RequiredStringValidators
stringLengthFields no Add list of StringLengthFieldValidators
urls no Add list of UrlValidators
visitorFields no Add list of VisitorFieldValidators
regexFields no Add list of RegexFieldValidator
expressions no Add list of ExpressionValidator
+ * + * + *

Example code: + * + *

+ * 
+ * @Validations(
+ *           requiredFields =
+ *                   {@RequiredFieldValidator(type = ValidatorType.SIMPLE, fieldName = "customfield", message = "You must enter a value for field.")},
+ *           requiredStrings =
+ *                   {@RequiredStringValidator(type = ValidatorType.SIMPLE, fieldName = "stringisrequired", message = "You must enter a value for string.")},
+ *           emails =
+ *                   { @EmailValidator(type = ValidatorType.SIMPLE, fieldName = "emailaddress", message = "You must enter a value for email.")},
+ *           urls =
+ *                   { @UrlValidator(type = ValidatorType.SIMPLE, fieldName = "hreflocation", message = "You must enter a value for email.")},
+ *           stringLengthFields =
+ *                   {@StringLengthFieldValidator(type = ValidatorType.SIMPLE, trim = true, minLength="10" , maxLength = "12", fieldName = "needstringlength", message = "You must enter a stringlength.")},
+ *           intRangeFields =
+ *                   { @IntRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "intfield", min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.")},
+ *           dateRangeFields =
+ *                   {@DateRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "datefield", min = "-1", max = "99", message = "bar must be between ${min} and ${max}, current value is ${bar}.")},
+ *           expressions = {
+ *               @ExpressionValidator(expression = "foo > 1", message = "Foo must be greater than Bar 1. Foo = ${foo}, Bar = ${bar}."),
+ *               @ExpressionValidator(expression = "foo > 2", message = "Foo must be greater than Bar 2. Foo = ${foo}, Bar = ${bar}."),
+ *               @ExpressionValidator(expression = "foo > 3", message = "Foo must be greater than Bar 3. Foo = ${foo}, Bar = ${bar}."),
+ *               @ExpressionValidator(expression = "foo > 4", message = "Foo must be greater than Bar 4. Foo = ${foo}, Bar = ${bar}."),
+ *               @ExpressionValidator(expression = "foo > 5", message = "Foo must be greater than Bar 5. Foo = ${foo}, Bar = ${bar}.")
+ *   }
+ *   )
+ *   public String execute() throws Exception {
+ *       return SUCCESS;
+ *   }
+ * 
+ * 
+ * + * @author jepjep + * @author Rainer Hermanns + * @version $Id$ + */ +@Target( { ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Validations { + + /** + * Custom Validation rules. + */ + public CustomValidator[] customValidators() default {}; + + public ConversionErrorFieldValidator[] conversionErrorFields() default {}; + + public DateRangeFieldValidator[] dateRangeFields() default {}; + + public EmailValidator[] emails() default {}; + + public FieldExpressionValidator[] fieldExpressions() default {}; + + public IntRangeFieldValidator[] intRangeFields() default {}; + + public RequiredFieldValidator[] requiredFields() default {}; + + public RequiredStringValidator[] requiredStrings() default {}; + + public StringLengthFieldValidator[] stringLengthFields() default {}; + + public UrlValidator[] urls() default {}; + + public ConditionalVisitorFieldValidator[] conditionalVisitorFields() default {}; + + public VisitorFieldValidator[] visitorFields() default {}; + + public RegexFieldValidator[] regexFields() default {}; + + public ExpressionValidator[] expressions() default {}; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ValidatorType.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ValidatorType.java new file mode 100644 index 000000000..aed95d71f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/ValidatorType.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.validator.annotations; + +/** + * ValidatorType + * + * @author Rainer Hermanns + * @version $Id$ + */ +public enum ValidatorType { + + FIELD, SIMPLE; + + @Override + public String toString() { + return super.toString().toUpperCase(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/VisitorFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/VisitorFieldValidator.java new file mode 100644 index 000000000..7274aed35 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/VisitorFieldValidator.java @@ -0,0 +1,150 @@ +/* + * 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.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * The validator allows you to forward validator to object properties of your action + * using the objects own validator files. This allows you to use the ModelDriven development + * pattern and manage your validations for your models in one place, where they belong, next to + * your model classes. + * + * The VisitorFieldValidator can handle either simple Object properties, Collections of Objects, or Arrays. + * The error message for the VisitorFieldValidator will be appended in front of validator messages added + * by the validations for the Object message. + * + * + *

Annotation usage: + * + * + *

The annotation must be applied at method level. + * + * + *

Annotation parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Parameter Required Default Notes
messageyes field error message
keyno i18n key from language specific properties file.
fieldNameno  
shortCircuitnofalseIf this validator should be used as shortCircuit.
context no action alias Determines the context to use for validating the Object property. If not defined, the context of the Action validation is propogated to the Object property validation. In the case of Action validation, this context is the Action alias.
appendPrefix no true Determines whether the field name of this field validator should be prepended to the field name of the visited field to determine the full field name when an error occurs. For example, suppose that the bean being validated has a "name" property. If appendPrefix is true, then the field error will be stored under the field "bean.name". If appendPrefix is false, then the field error will be stored under the field "name".
If you are using the VisitorFieldValidator to validate the model from a ModelDriven Action, you should set appendPrefix to false unless you are using "model.name" to reference the properties on your model.
+ * + * + *

Example code: + * + *

+ * 
+ * @VisitorFieldValidator(message = "Default message", key = "i18n.key", shortCircuit = true, context = "action alias", appendPrefix = true)
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @version $Id$ + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface VisitorFieldValidator { + + /** + * Determines the context to use for validating the Object property. + * If not defined, the context of the Action validator is propogated to the Object property validator. + * In the case of Action validator, this context is the Action alias. + */ + String context() default ""; + + /** + * Determines whether the field name of this field validator should be prepended to the field name of + * the visited field to determine the full field name when an error occurs. For example, suppose that + * the bean being validated has a "name" property. + * + * If appendPrefix is true, then the field error will be stored under the field "bean.name". + * If appendPrefix is false, then the field error will be stored under the field "name". + * + * If you are using the VisitorFieldValidator to validate the model from a ModelDriven Action, + * you should set appendPrefix to false unless you are using "model.name" to reference the properties + * on your model. + */ + boolean appendPrefix() default true; + + /** + * The default error message for this validator. + * NOTE: It is required to set a message, if you are not using the message key for 18n lookup! + */ + String message() default ""; + + /** + * The message key to lookup for i18n. + */ + String key() default ""; + + /** + * The optional fieldName for SIMPLE validator types. + */ + String fieldName() default ""; + + /** + * If this is activated, the validator will be used as short-circuit. + * + * Adds the short-circuit="true" attribute value if true. + * + */ + boolean shortCircuit() default false; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/package.html new file mode 100644 index 000000000..ff910e788 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/annotations/package.html @@ -0,0 +1 @@ +Validator annotations. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/AbstractFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/AbstractFieldValidatorDescription.java new file mode 100644 index 000000000..076e3b8aa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/AbstractFieldValidatorDescription.java @@ -0,0 +1,133 @@ +/* + * 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.validator.metadata; + +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +/** + * AbstractFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public abstract class AbstractFieldValidatorDescription implements ValidatorDescription { + + /** + * Jakarta commons-logging reference. + */ + protected static Logger log = null; + + public String fieldName; + public String key; + public String message; + public boolean shortCircuit; + public boolean simpleValidator; + + + public AbstractFieldValidatorDescription() { + log = LoggerFactory.getLogger(this.getClass()); + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public AbstractFieldValidatorDescription(String fieldName) { + this.fieldName = fieldName; + log = LoggerFactory.getLogger(this.getClass()); + } + + /** + * Sets the field name for this validator rule. + * + * @return fieldName the field name for this validator rule + */ + public String getFieldName() { + return fieldName; + } + + /** + * Sets the field name for this validator rule. + * + * @param fieldName the field name for this validator rule + */ + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + /** + * Sets the I18N message key. + * @param key the I18N message key + */ + public void setKey(String key) { + this.key = key; + } + + /** + * Sets the default validator failure message. + * + * @param message the default validator failure message + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Set the shortCircuit flag. + * + * @param shortCircuit the shortCircuit flag. + */ + public void setShortCircuit(boolean shortCircuit) { + this.shortCircuit = shortCircuit; + } + + public void setSimpleValidator(boolean simpleValidator) { + this.simpleValidator = simpleValidator; + } + + public boolean isSimpleValidator() { + return simpleValidator; + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + public String asXml() { + if ( simpleValidator) { + return asSimpleXml(); + } + return asFieldXml(); + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + public abstract String asFieldXml(); + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + public abstract String asSimpleXml(); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ConversionErrorFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ConversionErrorFieldValidatorDescription.java new file mode 100644 index 000000000..ce62cfc5c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ConversionErrorFieldValidatorDescription.java @@ -0,0 +1,118 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * ConversionErrorFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class ConversionErrorFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public ConversionErrorFieldValidatorDescription() { + super(); + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public ConversionErrorFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if (shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + + if (!"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if (shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName + ""); + + if (!"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/DateRangeFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/DateRangeFieldValidatorDescription.java new file mode 100644 index 000000000..6d41713f0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/DateRangeFieldValidatorDescription.java @@ -0,0 +1,140 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * DateRangeFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class DateRangeFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public String min; + public String max; + + public DateRangeFieldValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public DateRangeFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setMin(String min) { + this.min = min; + } + + public void setMax(String max) { + this.max = max; + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + if ( min != null && min.length() > 0) { + writer.println("\t\t\t" + min + ""); + } + if ( max != null && max.length() > 0) { + writer.println("\t\t\t" + max + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + + if ( min != null && min.length() > 0) { + writer.println("\t\t" + min + ""); + } + if ( max != null && max.length() > 0) { + writer.println("\t\t" + max + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/DoubleRangeFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/DoubleRangeFieldValidatorDescription.java new file mode 100644 index 000000000..5b4515692 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/DoubleRangeFieldValidatorDescription.java @@ -0,0 +1,142 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * DoubleRangeFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class DoubleRangeFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public String min; + public String max; + + public DoubleRangeFieldValidatorDescription() { + } + + /** + * Creates an DoubleRangeFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public DoubleRangeFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setMin(String min) { + this.min = min; + } + + public void setMax(String max) { + this.max = max; + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + if ( min != null && min.length() > 0) { + writer.println("\t\t\t" + min + ""); + } + if ( max != null && max.length() > 0) { + writer.println("\t\t\t" + max + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + + if ( min != null && min.length() > 0) { + writer.println("\t\t" + min + ""); + } + if ( max != null && max.length() > 0) { + writer.println("\t\t" + max + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/EmailValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/EmailValidatorDescription.java new file mode 100644 index 000000000..134be4f98 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/EmailValidatorDescription.java @@ -0,0 +1,119 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * EmailValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class EmailValidatorDescription extends AbstractFieldValidatorDescription { + + + public EmailValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public EmailValidatorDescription(String fieldName) { + super(fieldName); + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + + + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ExpressionValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ExpressionValidatorDescription.java new file mode 100644 index 000000000..72e263afa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ExpressionValidatorDescription.java @@ -0,0 +1,94 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * ExpressionValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class ExpressionValidatorDescription implements ValidatorDescription { + + public String expression; + public String key; + public String message; + public boolean shortCircuit; + + public void setExpression(String expression) { + this.expression = expression; + } + + public void setKey(String key) { + this.key = key; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setShortCircuit(boolean shortCircuit) { + this.shortCircuit = shortCircuit; + } + + /** + * Returns the field name to create the validation rule for. + * + * @return The field name to create the validation rule for + */ + public String getFieldName() { + throw new UnsupportedOperationException("ExpressionValidator annotations cannot be applied to fields..."); + } + + public String asXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + expression+ ""); + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + + public boolean isSimpleValidator() { + return false; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/FieldExpressionValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/FieldExpressionValidatorDescription.java new file mode 100644 index 000000000..1e5e84e1b --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/FieldExpressionValidatorDescription.java @@ -0,0 +1,114 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * FieldExpressionValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class FieldExpressionValidatorDescription extends AbstractFieldValidatorDescription { + + public String expression; + public String key; + public String message; + public boolean shortCircuit; + + public FieldExpressionValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public FieldExpressionValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setExpression(String expression) { + this.expression = expression; + } + + @Override + public void setKey(String key) { + this.key = key; + } + + @Override + public void setMessage(String message) { + this.message = message; + } + + @Override + public void setShortCircuit(boolean shortCircuit) { + this.shortCircuit = shortCircuit; + } + + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + + writer.println("\t\t\t" + expression+ ""); + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + throw new UnsupportedOperationException(getClass().getName() + " cannot be used for simple validators..."); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/IntRangeFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/IntRangeFieldValidatorDescription.java new file mode 100644 index 000000000..5cc403b3d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/IntRangeFieldValidatorDescription.java @@ -0,0 +1,145 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * IntRangeFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class IntRangeFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public String min; + public String max; + + public IntRangeFieldValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public IntRangeFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setMin(String min) { + this.min = min; + } + + public void setMax(String max) { + this.max = max; + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + if ( min != null && min.length() > 0) { + writer.println("\t\t\t" + min + ""); + } + if ( max != null && max.length() > 0) { + writer.println("\t\t\t" + max + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + + if ( min != null && min.length() > 0) { + writer.println("\t\t" + min + ""); + } + if ( max != null && max.length() > 0) { + writer.println("\t\t" + max + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + + + + + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/RequiredFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/RequiredFieldValidatorDescription.java new file mode 100644 index 000000000..426cd6948 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/RequiredFieldValidatorDescription.java @@ -0,0 +1,116 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * RequiredFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class RequiredFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public RequiredFieldValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public RequiredFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/RequiredStringValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/RequiredStringValidatorDescription.java new file mode 100644 index 000000000..5868d87fa --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/RequiredStringValidatorDescription.java @@ -0,0 +1,129 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * RequiredStringValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class RequiredStringValidatorDescription extends AbstractFieldValidatorDescription { + + public boolean trim = true; + + public RequiredStringValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public RequiredStringValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setTrim(boolean trim) { + this.trim = trim; + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + if ( !trim) { + writer.println("\t\t\t" + trim + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + if ( !trim) { + writer.println("\t\t" + trim + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/StringLengthFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/StringLengthFieldValidatorDescription.java new file mode 100644 index 000000000..878937e84 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/StringLengthFieldValidatorDescription.java @@ -0,0 +1,153 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * StringLengthFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class StringLengthFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public boolean trim = true; + public String minLength; + public String maxLength; + + public StringLengthFieldValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public StringLengthFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setTrim(boolean trim) { + this.trim = trim; + } + + public void setMinLength(String minLength) { + this.minLength = minLength; + } + + public void setMaxLength(String maxLength) { + this.maxLength = maxLength; + } + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + if ( !trim) { + writer.println("\t\t\t" + trim + ""); + } + if ( minLength != null && minLength.length() > 0) { + writer.println("\t\t\t" + minLength + ""); + } + if ( maxLength != null && maxLength.length() > 0) { + writer.println("\t\t\t" + maxLength + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + if ( !trim) { + writer.println("\t\t" + trim + ""); + } + + if ( minLength != null && minLength.length() > 0) { + writer.println("\t\t" + minLength + ""); + } + if ( maxLength != null && maxLength.length() > 0) { + writer.println("\t\t" + maxLength + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/URLValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/URLValidatorDescription.java new file mode 100644 index 000000000..6fe03e57a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/URLValidatorDescription.java @@ -0,0 +1,117 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * URLValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class URLValidatorDescription extends AbstractFieldValidatorDescription { + + + public URLValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified aliasNames. + * + * @param fieldName + */ + public URLValidatorDescription(String fieldName) { + super(fieldName); + } + + + /** + * Returns the field validator XML definition. + * + * @return the field validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t"); + } else { + writer.println("\t"); + } + + writer.println("\t\t" + fieldName+ ""); + + if ( !"".equals(key)) { + writer.println("\t\t" + message + ""); + } else { + writer.println("\t\t" + message + ""); + } + + writer.println("\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ValidatorDescription.java new file mode 100644 index 000000000..cbf6f6224 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/ValidatorDescription.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.validator.metadata; + +/** + * ValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public interface ValidatorDescription { + + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + String asXml(); + + /** + * Returns the field name to create the validation rule for. + * + * @return The field name to create the validation rule for + */ + String getFieldName(); + + /** + * Sets the I18N message key. + * @param key the I18N message key + */ + void setKey(String key); + + /** + * Sets the default validator failure message. + * + * @param message the default validator failure message + */ + void setMessage(String message); + + /** + * Set the shortCircuit flag. + * + * @param shortCircuit the shortCircuit flag. + */ + void setShortCircuit(boolean shortCircuit); + + boolean isSimpleValidator(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/VisitorFieldValidatorDescription.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/VisitorFieldValidatorDescription.java new file mode 100644 index 000000000..eb618e93d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/VisitorFieldValidatorDescription.java @@ -0,0 +1,105 @@ +/* + * 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.validator.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * VisitorFieldValidatorDescription + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class VisitorFieldValidatorDescription extends AbstractFieldValidatorDescription { + + public String context; + public boolean appendPrefix = true; + + public VisitorFieldValidatorDescription() { + } + + /** + * Creates an AbstractFieldValidatorDescription with the specified field name. + * + * @param fieldName + */ + public VisitorFieldValidatorDescription(String fieldName) { + super(fieldName); + } + + public void setContext(String context) { + this.context = context; + } + + public void setAppendPrefix(boolean appendPrefix) { + this.appendPrefix = appendPrefix; + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asFieldXml() { + StringWriter sw = new StringWriter(); + PrintWriter writer = null; + + try { + writer = new PrintWriter(sw); + + if ( shortCircuit) { + writer.println("\t\t"); + } else { + writer.println("\t\t"); + } + + if ( context != null && context.length() > 0) { + writer.println("\t\t\t" + context + ""); + } + + if ( !appendPrefix) { + writer.println("\t\t\t" + appendPrefix + ""); + } + + if ( !"".equals(key)) { + writer.println("\t\t\t" + message + ""); + } else { + writer.println("\t\t\t" + message + ""); + } + + writer.println("\t\t"); + + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + return sw.toString(); + } + + /** + * Returns the validator XML definition. + * + * @return the validator XML definition. + */ + @Override + public String asSimpleXml() { + throw new UnsupportedOperationException(getClass().getName() + " cannot be used for simple validators..."); + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/package.html new file mode 100644 index 000000000..f0215156c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/metadata/package.html @@ -0,0 +1 @@ +Validator meta data classes. diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/package.html new file mode 100644 index 000000000..07e7894e5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/package.html @@ -0,0 +1 @@ +XWork validation subsystem. \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/AbstractRangeValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/AbstractRangeValidator.java new file mode 100644 index 000000000..271209d54 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/AbstractRangeValidator.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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + + +/** + * Base class for range based validators. + * + * @author Jason Carreira + * @author Cameron Braid + */ +public abstract class AbstractRangeValidator extends FieldValidatorSupport { + + public void validate(Object object) throws ValidationException { + Object obj = getFieldValue(getFieldName(), object); + Comparable value = (Comparable) obj; + + // if there is no value - don't do comparison + // if a value is required, a required validator should be added to the field + if (value == null) { + return; + } + + // only check for a minimum value if the min parameter is set + if ((getMinComparatorValue() != null) && (value.compareTo(getMinComparatorValue()) < 0)) { + addFieldError(getFieldName(), object); + } + + // only check for a maximum value if the max parameter is set + if ((getMaxComparatorValue() != null) && (value.compareTo(getMaxComparatorValue()) > 0)) { + addFieldError(getFieldName(), object); + } + } + + protected abstract Comparable getMaxComparatorValue(); + + protected abstract Comparable getMinComparatorValue(); +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ConditionalVisitorFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ConditionalVisitorFieldValidator.java new file mode 100644 index 000000000..fe11e839c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ConditionalVisitorFieldValidator.java @@ -0,0 +1,77 @@ +package com.opensymphony.xwork2.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + +/** + * ConditionalVisitorFieldValidator + * + * + * <field name="colleaguePosition"> + * <field-validator type="fieldexpression" short-circuit="true"> + * reason == 'colleague' and colleaguePositionID == '_CHOOSE_' + * <message>You must choose a position where you worked with this person, + * or choose "Other..."</message> + * </field-validator> + * <field-validator type="conditionalvisitor"> + * reason == 'colleague' and colleaguePositionID == 'OTHER' + * <message/> + * </field-validator> + * </field> + * + * @author Matt Raible + */ +public class ConditionalVisitorFieldValidator extends VisitorFieldValidator { + private String expression; + + public void setExpression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return expression; + } + + /** + * If expression evaluates to true, invoke visitor validation. + * + * @param object the object being validated + * @throws ValidationException + */ + @Override + public void validate(Object object) throws ValidationException { + if (validateExpression(object)) { + super.validate(object); + } + } + + /** + * Validate the expression contained in the "expression" paramter. + * + * @param object the object you're validating + * @return true if expression evaluates to true (implying a validation + * failure) + * @throws ValidationException if anything goes wrong + */ + public boolean validateExpression(Object object) throws ValidationException { + Boolean answer = Boolean.FALSE; + Object obj = null; + + try { + obj = getFieldValue(expression, object); + } + catch (ValidationException e) { + throw e; + } + catch (Exception e) { + // let this pass, but it will be logged right below + } + + if ((obj != null) && (obj instanceof Boolean)) { + answer = (Boolean) obj; + } else { + log.warn("Got result of " + obj + " when trying to get Boolean."); + } + + return answer; + } +} \ No newline at end of file diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ConversionErrorFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ConversionErrorFieldValidator.java new file mode 100644 index 000000000..b1deb5968 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ConversionErrorFieldValidator.java @@ -0,0 +1,83 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.validator.ValidationException; + +import java.util.Map; + + +/** + * + * Field Validator that checks if a conversion error occured for this field. + * + *

+ * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
+ * + * + * + *
+ *     <!-- Plain Validator Syntax -->
+ *     <validator type="conversion">
+ *     		<param name="fieldName">myField</param>
+ *          <message>Conversion Error Occurred</message>
+ *     </validator>
+ *      
+ *     <!-- Field Validator Syntax -->
+ *     <field name="myField">
+ *        <field-validator type="conversion">
+ *           <message>Conversion Error Occurred</message>
+ *        </field-validator>
+ *     </field>
+ * 
+ * + * + * @author Jason Carreira + * @author tm_jee + * + * @version $Date $Id$ + */ +public class ConversionErrorFieldValidator extends RepopulateConversionErrorFieldValidatorSupport { + + /** + * The validation implementation must guarantee that setValidatorContext will + * be called with a non-null ValidatorContext before validate is called. + * + * @param object + * @throws ValidationException + */ + @Override + public void doValidate(Object object) throws ValidationException { + String fieldName = getFieldName(); + String fullFieldName = getValidatorContext().getFullFieldName(fieldName); + ActionContext context = ActionContext.getContext(); + Map conversionErrors = context.getConversionErrors(); + + if (conversionErrors.containsKey(fullFieldName)) { + if ((defaultMessage == null) || ("".equals(defaultMessage.trim()))) { + defaultMessage = XWorkConverter.getConversionErrorMessage(fullFieldName, context.getValueStack()); + } + + addFieldError(fieldName, object); + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/DateRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/DateRangeFieldValidator.java new file mode 100644 index 000000000..24d30b925 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/DateRangeFieldValidator.java @@ -0,0 +1,104 @@ +/* + * 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.validator.validators; + +import java.util.Date; + + +/** + * + * + * Field Validator that checks if the date supplied is within a specific range. + * + * NOTE: If no date converter is specified, XWorkBasicConverter will kick + * in to do the date conversion, which by default using the Date.SHORT format using + * the a programmatically specified locale else falling back to the system + * default locale. + * + * + * + * + *

+ * + * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • min - the min date range. If not specified will not be checked.
  • + *
  • max - the max date range. If not specified will not be checked.
  • + *
+ * + * + * + *
+ * 
+ *    <validators>
+ *    		<!-- Plain Validator syntax -->
+ *    		<validator type="date">
+ *    	        <param name="fieldName">birthday</param>
+ *              <param name="min">01/01/1990</param>
+ *              <param name="max">01/01/2000</param>
+ *              <message>Birthday must be within ${min} and ${max}</message>
+ *    		</validator>
+ *    
+ *          <!-- Field Validator Syntax -->
+ *          <field name="birthday">
+ *          	<field-validator type="date">
+ *           	    <param name="min">01/01/1990</param>
+ *                  <param name="max">01/01/2000</param>
+ *                  <message>Birthday must be within ${min} and ${max}</message>
+ *          	</field>
+ *          </field>
+ *    
+ *    </validators>
+ * 
+ * 
+ * + * + * @author Jason Carreira + * @version $Date$ $Id$ + */ +public class DateRangeFieldValidator extends AbstractRangeValidator { + + private Date max; + private Date min; + + + public void setMax(Date max) { + this.max = max; + } + + public Date getMax() { + return max; + } + + public void setMin(Date min) { + this.min = min; + } + + public Date getMin() { + return min; + } + + @Override + protected Comparable getMaxComparatorValue() { + return max; + } + + @Override + protected Comparable getMinComparatorValue() { + return min; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/DoubleRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/DoubleRangeFieldValidator.java new file mode 100644 index 000000000..39ffe30f0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/DoubleRangeFieldValidator.java @@ -0,0 +1,160 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + +/** + * + * Field Validator that checks if the double specified is within a certain range. + * + * + * + * + *
    + *
  • fieldName - The field name this validator is validating. Required if using +Plain-Validator Syntax otherwise not required
  • + *
  • minInclusive - the minimum inclusive value in FloatValue format specified by Java language (if none is specified, it will +not be checked)
  • + *
  • maxInclusive - the maximum inclusive value in FloatValue format specified by Java language (if none is specified, it will +not be checked)
  • + *
  • minExclusive - the minimum exclusive value in FloatValue format specified by Java language (if none is specified, it will +not be checked)
  • + *
  • maxExclusive - the maximum exclusive value in FloatValue format specified by Java language (if none is specified, it will +not be checked)
  • + *
+ * + * + * + *
+ * 
+ *                 <validators>
+ *           <!-- Plain Validator Syntax -->
+ *           <validator type="double">
+ *               <param name="fieldName">percentage</param>
+ *               <param name="minInclusive">20.1</param>
+ *               <param name="maxInclusive">50.1</param>
+ *               <message>Age needs to be between ${minInclusive} and
+${maxInclusive} (inclusive)</message>
+ *           </validator>
+ *
+ *           <!-- Field Validator Syntax -->
+ *           <field name="percentage">
+ *               <field-validator type="double">
+ *                   <param name="minExclusive">0.123</param>
+ *                   <param name="maxExclusive">99.98</param>
+ *                   <message>Percentage needs to be between ${minExclusive}
+and ${maxExclusive} (exclusive)</message>
+ *               </field-validator>
+ *           </field>
+ *      </validators>
+ * 
+ * 
+ * + * @author Rainer Hermanns + * @author Rene Gielen + * + * @version $Id$ + */ +// START SNIPPET: field-level-validator +public class DoubleRangeFieldValidator extends FieldValidatorSupport { + + String maxInclusive = null; + String minInclusive = null; + String minExclusive = null; + String maxExclusive = null; + + Double maxInclusiveValue = null; + Double minInclusiveValue = null; + Double minExclusiveValue = null; + Double maxExclusiveValue = null; + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + Double value; + try { + Object obj = this.getFieldValue(fieldName, object); + if (obj == null) { + return; + } + value = Double.valueOf(obj.toString()); + } catch (NumberFormatException e) { + return; + } + + parseParameterValues(); + if ((maxInclusiveValue != null && value.compareTo(maxInclusiveValue) > 0) || + (minInclusiveValue != null && value.compareTo(minInclusiveValue) < 0) || + (maxExclusiveValue != null && value.compareTo(maxExclusiveValue) >= 0) || + (minExclusiveValue != null && value.compareTo(minExclusiveValue) <= 0)) { + addFieldError(fieldName, object); + } + } + + private void parseParameterValues() { + this.minInclusiveValue = parseDouble(minInclusive); + this.maxInclusiveValue = parseDouble(maxInclusive); + this.minExclusiveValue = parseDouble(minExclusive); + this.maxExclusiveValue = parseDouble(maxExclusive); + } + + private Double parseDouble (String value) { + if (value != null) { + try { + return Double.valueOf(value); + } catch (NumberFormatException e) { + if (log.isWarnEnabled()) { + log.warn("DoubleRangeFieldValidator - [parseDouble]: Unable to parse given double parameter " + value); + } + } + } + return null; + } + + public void setMaxInclusive(String maxInclusive) { + this.maxInclusive = maxInclusive; + } + + public String getMaxInclusive() { + return maxInclusive; + } + + public void setMinInclusive(String minInclusive) { + this.minInclusive = minInclusive; + } + + public String getMinInclusive() { + return minInclusive; + } + + public String getMinExclusive() { + return minExclusive; + } + + public void setMinExclusive(String minExclusive) { + this.minExclusive = minExclusive; + } + + public String getMaxExclusive() { + return maxExclusive; + } + + public void setMaxExclusive(String maxExclusive) { + this.maxExclusive = maxExclusive; + } +} +// END SNIPPET: field-level-validator diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/EmailValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/EmailValidator.java new file mode 100644 index 000000000..1fe9950e6 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/EmailValidator.java @@ -0,0 +1,77 @@ +/* + * 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.validator.validators; + + +/** + * + * EmailValidator checks that a given String field, if not empty, + * is a valid email address. + *

+ *

+ * The regular expression used to validate that the string is an email address + * is: + *

+ *
+ * \\b(^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-])+((\\.com)|(\\.net)|(\\.org)|(\\.info)|(\\.edu)|(\\.mil)|(\\.gov)|(\\.biz)|(\\.ws)|(\\.us)|(\\.tv)|(\\.cc)|(\\.aero)|(\\.arpa)|(\\.coop)|(\\.int)|(\\.jobs)|(\\.museum)|(\\.name)|(\\.pro)|(\\.travel)|(\\.nato)|(\\..{2,3})|(\\..{2,3}\\..{2,3}))$)\\b
+ * 
+ * + * + * + * + *
    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
+ * + * + * + *
+ * 
+ *     <!-- Plain Validator Syntax -->
+ *     <validators>
+ *         <validator type="email">
+ *             <param name="fieldName">myEmail</param>
+ *             <message>Must provide a valid email</message>
+ *         </validator>
+ *     </validators>
+ *     
+ *     <!-- Field Validator Syntax -->
+ *     <field name="myEmail">
+ *        <field-validator type="email">
+ *           <message>Must provide a valid email</message>
+ *        </field-validator>
+ *     </field>
+ * 
+ * 
+ * + * @author jhouse + * @author tm_jee + * @version $Date$ $Id$ + */ +public class EmailValidator extends RegexFieldValidator { + + // see XW-371 + public static final String emailAddressPattern = + "\\b(^['_A-Za-z0-9-]+(\\.['_A-Za-z0-9-]+)*@([A-Za-z0-9-])+(\\.[A-Za-z0-9-]+)*((\\.[A-Za-z0-9]{2,})|(\\.[A-Za-z0-9]{2,}\\.[A-Za-z0-9]{2,}))$)\\b"; + + public EmailValidator() { + setExpression(emailAddressPattern); + setCaseSensitive(false); + } + +} + + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ExpressionValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ExpressionValidator.java new file mode 100644 index 000000000..1ea3b0ac8 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ExpressionValidator.java @@ -0,0 +1,86 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + + +/** + * + * A Non-Field Level validator that validates based on regular expression supplied. + * + *

+ * + * + *

    + *
  • expression - the Ognl expression to be evaluated against the stack (Must evaluate to a Boolean)
  • + *
+ * + * + * + *
+ * 
+ *     <validators>
+ *           <validator type="expression">
+ *              <param name="expression"> .... </param>
+ *              <message>Failed to meet Ognl Expression  .... </message>
+ *           </validator>
+ *     </validators>
+ * 
+ * 
+ * + * @author Jason Carreira + */ +// START SNIPPET: global-level-validator +public class ExpressionValidator extends ValidatorSupport { + + private String expression; + + + public void setExpression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return expression; + } + + public void validate(Object object) throws ValidationException { + Boolean answer = Boolean.FALSE; + Object obj = null; + + try { + obj = getFieldValue(expression, object); + } catch (ValidationException e) { + throw e; + } catch (Exception e) { + // let this pass, but it will be logged right below + } + + if ((obj != null) && (obj instanceof Boolean)) { + answer = (Boolean) obj; + } else { + log.warn("Got result of " + obj + " when trying to get Boolean."); + } + + if (!answer.booleanValue()) { + if (log.isDebugEnabled()) log.debug("Validation failed on expression " + expression + " with validated object "+ object); + addActionError(object); + } + } +} +// END SNIPPET: global-level-validator + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/FieldExpressionValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/FieldExpressionValidator.java new file mode 100644 index 000000000..a06a7d325 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/FieldExpressionValidator.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + + +/** + * + * Validates a field using an OGNL expression. + * + *

+ * + * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • expression - The Ognl expression (must evaluate to a boolean) which is to be evalidated the stack
  • + *
+ * + * + *
+ * 
+ *    <!-- Plain Validator Syntax -->
+ *    <validators>
+ *        <!-- Plain Validator Syntax -->
+ *        <validator type="fieldexpression">
+ *           <param name="fieldName">myField</param>
+ *           <param name="expression"><![CDATA[#myCreditLimit > #myGirfriendCreditLimit]]></param>
+ *           <message>My credit limit should be MORE than my girlfriend</message>
+ *        <validator>
+ *        
+ *        <!-- Field Validator Syntax -->
+ *        <field name="myField">
+ *            <field-validator type="fieldexpression">
+ *                <param name="expression"><![CDATA[#myCreditLimit > #myGirfriendCreditLimit]]></param>
+ *                <message>My credit limit should be MORE than my girlfriend</message>
+ *            </field-validator>
+ *        </field>
+ *        
+ *    </vaidators>
+ * 
+ * 
+ * + * + * @author $Author$ + * @version $Revision$ + */ +public class FieldExpressionValidator extends FieldValidatorSupport { + + private String expression; + + + public void setExpression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return expression; + } + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + + Boolean answer = Boolean.FALSE; + Object obj = null; + + try { + obj = getFieldValue(expression, object); + } catch (ValidationException e) { + throw e; + } catch (Exception e) { + // let this pass, but it will be logged right below + } + + if ((obj != null) && (obj instanceof Boolean)) { + answer = (Boolean) obj; + } else { + log.warn("Got result of " + obj + " when trying to get Boolean."); + } + + if (!answer.booleanValue()) { + addFieldError(fieldName, object); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/FieldValidatorSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/FieldValidatorSupport.java new file mode 100644 index 000000000..4a3f147dc --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/FieldValidatorSupport.java @@ -0,0 +1,48 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.FieldValidator; + + +/** + * Base class for field validators. + * + * @author Jason Carreira + */ +public abstract class FieldValidatorSupport extends ValidatorSupport implements FieldValidator { + + private String fieldName; + private String type; + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getFieldName() { + return fieldName; + } + + @Override + public void setValidatorType(String type) { + this.type = type; + } + + @Override + public String getValidatorType() { + return type; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/IntRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/IntRangeFieldValidator.java new file mode 100644 index 000000000..b1bccc9fe --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/IntRangeFieldValidator.java @@ -0,0 +1,93 @@ +/* + * 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.validator.validators; + + +/** + * + * Field Validator that checks if the integer specified is within a certain range. + * + * + * + * + *
    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • min - the minimum value (if none is specified, it will not be checked)
  • + *
  • max - the maximum value (if none is specified, it will not be checked)
  • + *
+ * + * + * + *
+ * 
+ * 		<validators>
+ *           <!-- Plain Validator Syntax -->
+ *           <validator type="int">
+ *               <param name="fieldName">age</param>
+ *               <param name="min">20</param>
+ *               <param name="max">50</param>
+ *               <message>Age needs to be between ${min} and ${max}</message>
+ *           </validator>
+ *           
+ *           <!-- Field Validator Syntax -->
+ *           <field name="age">
+ *               <field-validator type="int">
+ *                   <param name="min">20</param>
+ *                   <param name="max">50</param>
+ *                   <message>Age needs to be between ${min} and ${max}</message>
+ *               </field-validator>
+ *           </field>
+ *      </validators>
+ * 
+ * 
+ * + * + * + * @author Jason Carreira + * @version $Date$ $Id$ + */ +public class IntRangeFieldValidator extends AbstractRangeValidator { + + Integer max = null; + Integer min = null; + + + public void setMax(Integer max) { + this.max = max; + } + + public Integer getMax() { + return max; + } + + @Override + public Comparable getMaxComparatorValue() { + return max; + } + + public void setMin(Integer min) { + this.min = min; + } + + public Integer getMin() { + return min; + } + + @Override + public Comparable getMinComparatorValue() { + return min; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/LongRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/LongRangeFieldValidator.java new file mode 100644 index 000000000..36f99b3fb --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/LongRangeFieldValidator.java @@ -0,0 +1,92 @@ +/* + * 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.validator.validators; + + +/** + * + * Field Validator that checks if the long specified is within a certain range. + * + * + * + * + *
    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • min - the minimum value (if none is specified, it will not be checked)
  • + *
  • max - the maximum value (if none is specified, it will not be checked)
  • + *
+ * + * + * + *
+ * 
+ *              <validators>
+ *           <!-- Plain Validator Syntax -->
+ *           <validator type="long">
+ *               <param name="fieldName">age</param>
+ *               <param name="min">20</param>
+ *               <param name="max">50</param>
+ *               <message>Age needs to be between ${min} and ${max}</message>
+ *           </validator>
+ *           
+ *           <!-- Field Validator Syntax -->
+ *           <field name="age">
+ *               <field-validator type="long">
+ *                   <param name="min">20</param>
+ *                   <param name="max">50</param>
+ *                   <message>Age needs to be between ${min} and ${max}</message>
+ *               </field-validator>
+ *           </field>
+ *      </validators>
+ * 
+ * 
+ * + * + * + * @version $Date$ + */ +public class LongRangeFieldValidator extends AbstractRangeValidator { + + Long max = null; + Long min = null; + + + public void setMax(Long max) { + this.max = max; + } + + public Long getMax() { + return max; + } + + @Override + public Comparable getMaxComparatorValue() { + return max; + } + + public void setMin(Long min) { + this.min = min; + } + + public Long getMin() { + return min; + } + + @Override + public Comparable getMinComparatorValue() { + return min; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RegexFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RegexFieldValidator.java new file mode 100644 index 000000000..6e6621015 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RegexFieldValidator.java @@ -0,0 +1,154 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * Validates a string field using a regular expression. + * + *

+ * + * + * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • expression - The RegExp expression REQUIRED
  • + *
  • caseSensitive - Boolean (Optional). Sets whether the expression should be matched against in a case-sensitive way. Default is true.
  • + *
  • trim - Boolean (Optional). Sets whether the expression should be trimed before matching. Default is true.
  • + *
+ * + * + * + *
+ * 
+ *    <validators>
+ *        <!-- Plain Validator Syntax -->
+ *        <validator type="regex">
+ *            <param name="fieldName">myStrangePostcode</param>
+ *            <param name="expression"><![CDATA[([aAbBcCdD][123][eEfFgG][456])]]<>/param>
+ *        </validator>
+ *    
+ *        <!-- Field Validator Syntax -->
+ *        <field name="myStrangePostcode">
+ *            <field-validator type="regex">
+ *               <param name="expression"><![CDATA[([aAbBcCdD][123][eEfFgG][456])]]></param>
+ *            </field-validator>
+ *        </field>
+ *    </validators>
+ * 
+ * 
+ * + * @author Quake Wang + * @version $Date$ $Revision$ + */ +public class RegexFieldValidator extends FieldValidatorSupport { + + private String expression; + private boolean caseSensitive = true; + private boolean trim = true; + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + Object value = this.getFieldValue(fieldName, object); + // if there is no value - don't do comparison + // if a value is required, a required validator should be added to the field + if (value == null || expression == null) { + return; + } + + // XW-375 - must be a string + if (!(value instanceof String)) { + return; + } + + // string must not be empty + String str = ((String) value).trim(); + if (str.length() == 0) { + return; + } + + // match against expression + Pattern pattern; + if (isCaseSensitive()) { + pattern = Pattern.compile(expression); + } else { + pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE); + } + + String compare = (String) value; + if ( trim ) { + compare = compare.trim(); + } + Matcher matcher = pattern.matcher( compare ); + + if (!matcher.matches()) { + addFieldError(fieldName, object); + } + } + + /** + * @return Returns the regular expression to be matched. + */ + public String getExpression() { + return expression; + } + + /** + * Sets the regular expression to be matched. + */ + public void setExpression(String expression) { + this.expression = expression; + } + + /** + * @return Returns whether the expression should be matched against in + * a case-sensitive way. Default is true. + */ + public boolean isCaseSensitive() { + return caseSensitive; + } + + /** + * Sets whether the expression should be matched against in + * a case-sensitive way. Default is true. + */ + public void setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + /** + * @return Returns whether the expression should be trimed before matching. + * Default is true. + */ + public boolean isTrimed() { + return trim; + } + + /** + * Sets whether the expression should be trimed before matching. + * Default is true. + */ + public void setTrim(boolean trim) { + this.trim = trim; + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RepopulateConversionErrorFieldValidatorSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RepopulateConversionErrorFieldValidatorSupport.java new file mode 100644 index 000000000..15c284b91 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RepopulateConversionErrorFieldValidatorSupport.java @@ -0,0 +1,200 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.validator.ValidationException; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * + * + * An abstract base class that adds in the capability to populate the stack with + * a fake parameter map when a conversion error has occurred and the 'repopulateField' + * property is set to "true". + * + *

+ * + * + * + * + * The capability of auto-repopulating the stack with a fake parameter map when + * a conversion error has occurred can be done with 'repopulateField' property + * set to "true". + * + *

+ * + * This is typically usefull when one wants to repopulate the field with the original value + * when a conversion error occurred. Eg. with a textfield that only allows an Integer + * (the action class have an Integer field declared), upon conversion error, the incorrectly + * entered integer (maybe a text 'one') will not appear when dispatched back. With 'repopulateField' + * porperty set to true, it will, meaning the textfield will have 'one' as its value + * upon conversion error. + * + * + * + *

+ * + *

+ * 
+ * 
+ * <!-- myJspPage.jsp -->
+ * <ww:form action="someAction" method="POST">
+ *   ....
+ *   <ww:textfield 
+ *       label="My Integer Field"
+ *       name="myIntegerField" />
+ *   ....
+ *   <ww:submit />       
+ * </ww:form>
+ * 
+ * 
+ * 
+ * + *
+ * 
+ * 
+ * <!-- xwork.xml -->
+ * <xwork>
+ * <include file="xwork-default.xml" />
+ * ....
+ * <package name="myPackage" extends="xwork-default">
+ *   ....
+ *   <action name="someAction" class="example.MyActionSupport.java">
+ *      <result name="input">myJspPage.jsp</result>
+ *      <result>success.jsp</result>
+ *   </action>
+ *   ....
+ * </package>
+ * ....
+ * </xwork>
+ * 
+ * 
+ * 
+ * + * + *
+ * 
+ * 
+ * <!-- MyActionSupport.java -->
+ * public class MyActionSupport extends ActionSupport {
+ *    private Integer myIntegerField;
+ *    
+ *    public Integer getMyIntegerField() { return this.myIntegerField; }
+ *    public void setMyIntegerField(Integer myIntegerField) { 
+ *       this.myIntegerField = myIntegerField; 
+ *    }
+ * }
+ * 
+ * 
+ * 
+ * + * + *
+ * 
+ * 
+ * <!-- MyActionSupport-someAction-validation.xml -->
+ * <validators>
+ *   ...
+ *   <field name="myIntegerField">
+ *      <field-validator type="conversion">
+ *         <param name="repopulateField">true</param>
+ *         <message>Conversion Error (Integer Wanted)</message>
+ *      </field-validator>
+ *   </field>
+ *   ...
+ * </validators>
+ * 
+ * 
+ * 
+ * + * @author tm_jee + * @version $Date$ $Id$ + */ +public abstract class RepopulateConversionErrorFieldValidatorSupport extends FieldValidatorSupport { + + private static final Logger LOG = LoggerFactory.getLogger(RepopulateConversionErrorFieldValidatorSupport.class); + + private String repopulateFieldAsString = "false"; + private boolean repopulateFieldAsBoolean = false; + + public String getRepopulateField() { + return repopulateFieldAsString; + } + + public void setRepopulateField(String repopulateField) { + this.repopulateFieldAsString = repopulateField == null ? repopulateField : repopulateField.trim(); + this.repopulateFieldAsBoolean = "true".equalsIgnoreCase(this.repopulateFieldAsString) ? (true) : (false); + } + + public void validate(Object object) throws ValidationException { + doValidate(object); + if (repopulateFieldAsBoolean) { + repopulateField(object); + } + } + + public void repopulateField(Object object) throws ValidationException { + + ActionInvocation invocation = ActionContext.getContext().getActionInvocation(); + Map conversionErrors = ActionContext.getContext().getConversionErrors(); + + String fieldName = getFieldName(); + String fullFieldName = getValidatorContext().getFullFieldName(fieldName); + if (conversionErrors.containsKey(fullFieldName)) { + Object value = conversionErrors.get(fullFieldName); + + final Map fakeParams = new LinkedHashMap(); + boolean doExprOverride = false; + + if (value instanceof String[]) { + // take the first element, if possible + String[] tmpValue = (String[]) value; + if (tmpValue != null && (tmpValue.length > 0)) { + doExprOverride = true; + fakeParams.put(fullFieldName, "'" + tmpValue[0] + "'"); + } else { + LOG.warn("value is an empty array of String or with first element in it as null [" + value + "], will not repopulate conversion error "); + } + } else if (value instanceof String) { + String tmpValue = (String) value; + doExprOverride = true; + fakeParams.put(fullFieldName, "'" + tmpValue + "'"); + } else { + // opps... it should be + LOG.warn("conversion error value is not a String or array of String but instead is [" + value + "], will not repopulate conversion error"); + } + + if (doExprOverride) { + invocation.addPreResultListener(new PreResultListener() { + public void beforeResult(ActionInvocation invocation, String resultCode) { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.setExprOverrides(fakeParams); + } + }); + } + } + } + + protected abstract void doValidate(Object object) throws ValidationException; +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RequiredFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RequiredFieldValidator.java new file mode 100644 index 000000000..6b103f275 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RequiredFieldValidator.java @@ -0,0 +1,72 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + + +/** + * + * RequiredFieldValidator checks if the specified field is not null. + * + *

+ * + * + * + *

    + *
  • fieldName - field name if plain-validator syntax is used, not needed if field-validator syntax is used
  • + *
+ * + * + * + *
+ * 
+ * 	   <validators>
+ * 
+ *         <!-- Plain Validator Syntax -->
+ *         <validator type="required">
+ *             <param name="fieldName">username</param>
+ *             <message>username must not be null</message>
+ *         </validator>
+ * 
+ * 
+ *         <!-- Field Validator Syntax -->
+ *         <field name="username">
+ *             <field-validator type="required">
+ *             	   <message>username must not be null</message>
+ *             </field-validator>
+ *         </field>
+ * 
+ *     </validators>
+ * 
+ * 
+ * + * + * + * @author rainerh + * @version $Revision$ + */ +public class RequiredFieldValidator extends FieldValidatorSupport { + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + Object value = this.getFieldValue(fieldName, object); + + if (value == null) { + addFieldError(fieldName, object); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RequiredStringValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RequiredStringValidator.java new file mode 100644 index 000000000..54971cd2a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RequiredStringValidator.java @@ -0,0 +1,92 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + + +/** + * + * RequiredStringValidator checks that a String field is non-null and has a length > 0. + * (i.e. it isn't ""). The "trim" parameter determines whether it will {@link String#trim() trim} + * the String before performing the length check. If unspecified, the String will be trimmed. + * + *

+ * + * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • trim - trim the field name value before validating (default is true)
  • + *
+ * + * + * + *
+ * 
+ *     <validators>
+ *         <!-- Plain-Validator Syntax -->
+ *         <validator type="requiredstring">
+ *             <param name="fieldName">username</param>
+ *             <param name="trim">true</param>
+ *             <message>username is required</message>
+ *         </validator>
+ *         
+ *         <!-- Field-Validator Syntax -->
+ *         <field name="username">
+ *         	  <field-validator type="requiredstring">
+ *                 <param name="trim">true</param>
+ *                 <message>username is required</message>
+ *            </field-validator>
+ *         </field>
+ *     </validators>
+ * 
+ * 
+ * + * @author rainerh + * @version $Date$ $Id$ + */ +public class RequiredStringValidator extends FieldValidatorSupport { + + private boolean doTrim = true; + + + public void setTrim(boolean trim) { + doTrim = trim; + } + + public boolean getTrim() { + return doTrim; + } + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + Object value = this.getFieldValue(fieldName, object); + + if (!(value instanceof String)) { + addFieldError(fieldName, object); + } else { + String s = (String) value; + + if (doTrim) { + s = s.trim(); + } + + if (s.length() == 0) { + addFieldError(fieldName, object); + } + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ShortRangeFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ShortRangeFieldValidator.java new file mode 100644 index 000000000..ab82977c0 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ShortRangeFieldValidator.java @@ -0,0 +1,92 @@ +/* + * 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.validator.validators; + + +/** + * + * Field Validator that checks if the short specified is within a certain range. + * + * + * + * + *
    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • min - the minimum value (if none is specified, it will not be checked)
  • + *
  • max - the maximum value (if none is specified, it will not be checked)
  • + *
+ * + * + * + *
+ * 
+ *              <validators>
+ *           <!-- Plain Validator Syntax -->
+ *           <validator type="short">
+ *               <param name="fieldName">age</param>
+ *               <param name="min">20</param>
+ *               <param name="max">50</param>
+ *               <message>Age needs to be between ${min} and ${max}</message>
+ *           </validator>
+ *           
+ *           <!-- Field Validator Syntax -->
+ *           <field name="age">
+ *               <field-validator type="short">
+ *                   <param name="min">20</param>
+ *                   <param name="max">50</param>
+ *                   <message>Age needs to be between ${min} and ${max}</message>
+ *               </field-validator>
+ *           </field>
+ *      </validators>
+ * 
+ * 
+ * + * + * + * @version $Date$ + */ +public class ShortRangeFieldValidator extends AbstractRangeValidator { + + Short max = null; + Short min = null; + + + public void setMax(Short max) { + this.max = max; + } + + public Short getMax() { + return max; + } + + @Override + public Comparable getMaxComparatorValue() { + return max; + } + + public void setMin(Short min) { + this.min = min; + } + + public Short getMin() { + return min; + } + + @Override + public Comparable getMinComparatorValue() { + return min; + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/StringLengthFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/StringLengthFieldValidator.java new file mode 100644 index 000000000..768c2c99c --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/StringLengthFieldValidator.java @@ -0,0 +1,125 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; + +/** + * + * StringLengthFieldValidator checks that a String field is of a certain length. If the "minLength" + * parameter is specified, it will make sure that the String has at least that many characters. If + * the "maxLength" parameter is specified, it will make sure that the String has at most that many + * characters. The "trim" parameter determines whether it will {@link String#trim() trim} the + * String before performing the length check. If unspecified, the String will be trimmed. + * + *

+ * + * + * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
  • maxLength - The max length of the field value. Default ignore.
  • + *
  • minLength - The min length of the field value. Default ignore.
  • + *
  • trim - Trim the field value before evaluating its min/max length. Default true
  • + *
+ * + * + * + *
+ * 
+ *      <validators>
+ *           <!-- Plain Validator Syntax -->
+ *           <validator type="stringlength">
+ *                <param name="fieldName">myPurchaseCode</param>
+ *                <param name="minLength">10</param>
+ *                <param name="maxLength">10</param>
+ *                <param name="trim">true</param>
+ *                <message>Your purchase code needs to be 10 characters long</message>		
+ *            </validator>
+ * 
+ *            <!-- Field Validator Syntax -->
+ *            <field name="myPurchaseCode">
+ *                <field-validator type="stringlength">
+ *                     <param name="minLength">10</param>
+ *                     <param name="maxLength">10</param>
+ *                     <param name="trim">true</param>
+ *                     <message>Your purchase code needs to be 10 characters long</message>
+ *                </field-validator>
+ *            </field>
+ *      </validators>
+ * 
+ * 
+ * + * + * @author Jason Carreira + * @author Mark Woon + * @author tmjee + * @version $Date$ $Id$ + */ +public class StringLengthFieldValidator extends FieldValidatorSupport { + + private boolean doTrim = true; + private int maxLength = -1; + private int minLength = -1; + + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public int getMaxLength() { + return maxLength; + } + + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public int getMinLength() { + return minLength; + } + + public void setTrim(boolean trim) { + doTrim = trim; + } + + public boolean getTrim() { + return doTrim; + } + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + String val = (String) getFieldValue(fieldName, object); + + if (val == null || val.length() <= 0) { + // use a required validator for these + return; + } + if (doTrim) { + val = val.trim(); + if (val.length() <= 0) { + // use a required validator + return; + } + } + + if ((minLength > -1) && (val.length() < minLength)) { + addFieldError(fieldName, object); + } else if ((maxLength > -1) && (val.length() > maxLength)) { + addFieldError(fieldName, object); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/URLValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/URLValidator.java new file mode 100644 index 000000000..fe679995d --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/URLValidator.java @@ -0,0 +1,80 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.validator.ValidationException; +import com.opensymphony.xwork2.util.URLUtil; + + +/** + * + * + * URLValidator checks that a given field is a String and a valid URL + * + * + * + *

+ * + * + * + *

    + *
  • fieldName - The field name this validator is validating. Required if using Plain-Validator Syntax otherwise not required
  • + *
+ * + * + * + *

+ * + *

+ * 
+ * 
+ *     <validators>
+ *          <!-- Plain Validator Syntax -->
+ *          <validator type="url">
+ *              <param name="fieldName">myHomePage</param>
+ *              <message>Invalid homepage url</message>
+ *          </validator>
+ *          
+ *          <!-- Field Validator Syntax -->
+ *          <field name="myHomepage">
+ *              <message>Invalid homepage url</message>
+ *          </field>
+ *     </validators>
+ *     
+ * 
+ * 
+ * + * + * @author $Author$ + * @version $Date$ $Revision$ + */ +public class URLValidator extends FieldValidatorSupport { + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + Object value = this.getFieldValue(fieldName, object); + + // if there is no value - don't do comparison + // if a value is required, a required validator should be added to the field + if (value == null || value.toString().length() == 0) { + return; + } + + if (!(value.getClass().equals(String.class)) || !URLUtil.verifyUrl((String) value)) { + addFieldError(fieldName, object); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ValidatorSupport.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ValidatorSupport.java new file mode 100644 index 000000000..91edd3b57 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/ValidatorSupport.java @@ -0,0 +1,213 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import com.opensymphony.xwork2.validator.*; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + + +/** + * Abstract implementation of the Validator interface suitable for subclassing. + * + * @author Jason Carreira + * @author tm_jee + * @author Martin Gilday + */ +public abstract class ValidatorSupport implements Validator, ShortCircuitableValidator { + + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + protected String defaultMessage = ""; + protected String messageKey; + private ValidatorContext validatorContext; + private boolean shortCircuit; + private boolean parse; + private String type; + private String[] messageParameters; + private ValueStack stack; + + + public void setValueStack(ValueStack stack) { + this.stack = stack; + } + + public void setDefaultMessage(String message) { + this.defaultMessage = message; + } + + public String getDefaultMessage() { + return defaultMessage; + } + + public void setParse(boolean parse) { + this.parse = parse; + } + + public boolean getParse() { + return parse; + } + + public String getMessage(Object object) { + String message; + boolean pop = false; + + if (!stack.getRoot().contains(object)) { + stack.push(object); + pop = true; + } + + stack.push(this); + + if (messageKey != null) { + if ((defaultMessage == null) || ("".equals(defaultMessage.trim()))) { + defaultMessage = messageKey; + } + if (validatorContext == null) { + validatorContext = new DelegatingValidatorContext(object); + } + List parsedMessageParameters = null; + if (messageParameters != null) { + parsedMessageParameters = new ArrayList(); + for (String messageParameter : messageParameters) { + if (messageParameter != null) { + try { + Object val = stack.findValue(messageParameter); + parsedMessageParameters.add(val); + } catch (Exception e) { + // if there's an exception in parsing, we'll just treat the expression itself as the + // parameter + log.warn("exception while parsing message parameter [" + messageParameter + "]", e); + parsedMessageParameters.add(messageParameter); + } + } + } + } + + message = validatorContext.getText(messageKey, defaultMessage, parsedMessageParameters); + + } else { + message = defaultMessage; + } + + if (StringUtils.isNotBlank(message)) + message = TextParseUtil.translateVariables(message, stack); + + stack.pop(); + + if (pop) { + stack.pop(); + } + + return message; + } + + public void setMessageKey(String key) { + messageKey = key; + } + + public String getMessageKey() { + return messageKey; + } + + public String[] getMessageParameters() { + return this.messageParameters; + } + + public void setMessageParameters(String[] messageParameters) { + this.messageParameters = messageParameters; + } + + public void setShortCircuit(boolean shortcircuit) { + shortCircuit = shortcircuit; + } + + public boolean isShortCircuit() { + return shortCircuit; + } + + public void setValidatorContext(ValidatorContext validatorContext) { + this.validatorContext = validatorContext; + } + + public ValidatorContext getValidatorContext() { + return validatorContext; + } + + public void setValidatorType(String type) { + this.type = type; + } + + public String getValidatorType() { + return type; + } + + /** + * Parse expression passed in against value stack. Only parse + * when 'parse' param is set to true, else just returns the expression unparsed. + * + * @param expression + * @return Object + */ + protected Object conditionalParse(String expression) { + if (parse) { + return TextParseUtil.translateVariables('$', expression, stack); + } + return expression; + } + + /** + * Return the field value named name from object, + * object should have the appropriate getter/setter. + * + * @param name + * @param object + * @return Object as field value + * @throws ValidationException + */ + protected Object getFieldValue(String name, Object object) throws ValidationException { + + boolean pop = false; + + if (!stack.getRoot().contains(object)) { + stack.push(object); + pop = true; + } + + Object retVal = stack.findValue(name); + + if (pop) { + stack.pop(); + } + + return retVal; + } + + protected void addActionError(Object object) { + validatorContext.addActionError(getMessage(object)); + } + + protected void addFieldError(String propertyName, Object object) { + validatorContext.addFieldError(propertyName, getMessage(object)); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/VisitorFieldValidator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/VisitorFieldValidator.java new file mode 100644 index 000000000..1310e10db --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/VisitorFieldValidator.java @@ -0,0 +1,216 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.ActionValidatorManager; +import com.opensymphony.xwork2.validator.DelegatingValidatorContext; +import com.opensymphony.xwork2.validator.ValidationException; +import com.opensymphony.xwork2.validator.ValidatorContext; + +import java.util.Collection; + + +/** + * + * The VisitorFieldValidator allows you to forward validation to object + * properties of your action using the object's own validation files. This + * allows you to use the ModelDriven development pattern and manage your + * validations for your models in one place, where they belong, next to your + * model classes. The VisitorFieldValidator can handle either simple Object + * properties, Collections of Objects, or Arrays. + * + *

+ * + * + *

    + *
  • fieldName - field name if plain-validator syntax is used, not needed if field-validator syntax is used
  • + *
  • context - the context of which validation should take place. Optional
  • + *
  • appendPrefix - the prefix to be added to field. Optional
  • + *
+ * + * + *
+ * 
+ *    <validators>
+ *        <!-- Plain Validator Syntax -->
+ *        <validator type="visitor">
+ *            <param name="fieldName">user</param>
+ *            <param name="context">myContext</param>
+ *            <param name="appendPrefix">true</param>
+ *        </validator>
+ *
+ *        <!-- Field Validator Syntax -->
+ *        <field name="user">
+ *           <field-validator type="visitor">
+ *              <param name="context">myContext</param>
+ *              <param name="appendPrefix">true</param>
+ *           </field-validator>
+ *        </field>
+ *    </validators>
+ * 
+ * 
+ * + * + *

In the example above, if the acion's getUser() method return User object, XWork + * will look for User-myContext-validation.xml for the validators. Since appednPrefix is true, + * every field name will be prefixed with 'user' such that if the actual field name for 'name' is + * 'user.name'

+ * + * + * @author Jason Carreira + * @author Rainer Hermanns + * @version $Date$ $Id$ + */ +public class VisitorFieldValidator extends FieldValidatorSupport { + + private String context; + private boolean appendPrefix = true; + private ActionValidatorManager actionValidatorManager; + + + @Inject + public void setActionValidatorManager(ActionValidatorManager mgr) { + this.actionValidatorManager = mgr; + } + + /** + * Sets whether the field name of this field validator should be prepended to the field name of + * the visited field to determine the full field name when an error occurs. The default is + * true. + */ + public void setAppendPrefix(boolean appendPrefix) { + this.appendPrefix = appendPrefix; + } + + /** + * Flags whether the field name of this field validator should be prepended to the field name of + * the visited field to determine the full field name when an error occurs. The default is + * true. + */ + public boolean isAppendPrefix() { + return appendPrefix; + } + + public void setContext(String context) { + this.context = context; + } + + public String getContext() { + return context; + } + + public void validate(Object object) throws ValidationException { + String fieldName = getFieldName(); + Object value = this.getFieldValue(fieldName, object); + if (value == null) { + log.warn("The visited object is null, VisitorValidator will not be able to handle validation properly. Please make sure the visited object is not null for VisitorValidator to function properly"); + return; + } + ValueStack stack = ActionContext.getContext().getValueStack(); + + stack.push(object); + + String visitorContext = (context == null) ? ActionContext.getContext().getName() : context; + + if (value instanceof Collection) { + Collection coll = (Collection) value; + Object[] array = coll.toArray(); + + validateArrayElements(array, fieldName, visitorContext); + } else if (value instanceof Object[]) { + Object[] array = (Object[]) value; + + validateArrayElements(array, fieldName, visitorContext); + } else { + validateObject(fieldName, value, visitorContext); + } + + stack.pop(); + } + + private void validateArrayElements(Object[] array, String fieldName, String visitorContext) throws ValidationException { + if (array == null) { + return; + } + + for (int i = 0; i < array.length; i++) { + Object o = array[i]; + if (o != null) { + validateObject(fieldName + "[" + i + "]", o, visitorContext); + } + } + } + + private void validateObject(String fieldName, Object o, String visitorContext) throws ValidationException { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(o); + + ValidatorContext validatorContext; + + if (appendPrefix) { + validatorContext = new AppendingValidatorContext(getValidatorContext(), o, fieldName, getMessage(o)); + } else { + ValidatorContext parent = getValidatorContext(); + validatorContext = new DelegatingValidatorContext(parent, DelegatingValidatorContext.makeTextProvider(o, parent), parent); + } + + actionValidatorManager.validate(o, visitorContext, validatorContext); + stack.pop(); + } + + + public static class AppendingValidatorContext extends DelegatingValidatorContext { + private String field; + private String message; + private ValidatorContext parent; + + public AppendingValidatorContext(ValidatorContext parent, Object object, String field, String message) { + super(parent, makeTextProvider(object, parent), parent); + + this.field = field; + this.message = message; + this.parent = parent; + } + + /** + * Translates a simple field name into a full field name in Ognl syntax + * + * @param fieldName field name in OGNL syntax + * @return field name in OGNL syntax + */ + @Override + public String getFullFieldName(String fieldName) { + return field + "." + fieldName; + } + + public String getFullFieldNameFromParent(String fieldName) { + return parent.getFullFieldName(field + "." + fieldName); + } + + @Override + public void addActionError(String anErrorMessage) { + super.addFieldError(getFullFieldName(field), message + anErrorMessage); + } + + @Override + public void addFieldError(String fieldName, String errorMessage) { + super.addFieldError(getFullFieldName(fieldName), message + errorMessage); + } + } +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/package.html b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/package.html new file mode 100644 index 000000000..8b34399a2 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/package.html @@ -0,0 +1 @@ +XWork default validator classes. diff --git a/xwork-core/src/main/resources/com/opensymphony/xwork2/validator/validators/default.xml b/xwork-core/src/main/resources/com/opensymphony/xwork2/validator/validators/default.xml new file mode 100644 index 000000000..3c164f510 --- /dev/null +++ b/xwork-core/src/main/resources/com/opensymphony/xwork2/validator/validators/default.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/com/opensymphony/xwork2/xwork-messages.properties b/xwork-core/src/main/resources/com/opensymphony/xwork2/xwork-messages.properties new file mode 100644 index 000000000..606887998 --- /dev/null +++ b/xwork-core/src/main/resources/com/opensymphony/xwork2/xwork-messages.properties @@ -0,0 +1,10 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +xwork.error.action.execution=Error during Action invocation +xwork.exception.missing-action=There is no Action mapped for action name {0}. +xwork.exception.missing-package-action=There is no Action mapped for namespace {0} and action name {1}. +xwork.default.invalid.fieldvalue=Invalid field value for field "{0}". + diff --git a/xwork-core/src/main/resources/overview.html b/xwork-core/src/main/resources/overview.html new file mode 100644 index 000000000..8a5dae3d4 --- /dev/null +++ b/xwork-core/src/main/resources/overview.html @@ -0,0 +1,3 @@ + +This document is the API specification for XWork 2.0. + diff --git a/xwork-core/src/main/resources/xwork-1.0.dtd b/xwork-core/src/main/resources/xwork-1.0.dtd new file mode 100644 index 000000000..4c6980376 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-1.0.dtd @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-1.1.1.dtd b/xwork-core/src/main/resources/xwork-1.1.1.dtd new file mode 100644 index 000000000..a03245acd --- /dev/null +++ b/xwork-core/src/main/resources/xwork-1.1.1.dtd @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-1.1.2.dtd b/xwork-core/src/main/resources/xwork-1.1.2.dtd new file mode 100644 index 000000000..fc00a39ab --- /dev/null +++ b/xwork-core/src/main/resources/xwork-1.1.2.dtd @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-1.1.dtd b/xwork-core/src/main/resources/xwork-1.1.dtd new file mode 100644 index 000000000..8f0df4bef --- /dev/null +++ b/xwork-core/src/main/resources/xwork-1.1.dtd @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-2.0.dtd b/xwork-core/src/main/resources/xwork-2.0.dtd new file mode 100644 index 000000000..5c8e7a23a --- /dev/null +++ b/xwork-core/src/main/resources/xwork-2.0.dtd @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-2.1.3.dtd b/xwork-core/src/main/resources/xwork-2.1.3.dtd new file mode 100644 index 000000000..e0f813cd5 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-2.1.3.dtd @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-2.1.dtd b/xwork-core/src/main/resources/xwork-2.1.dtd new file mode 100644 index 000000000..a73604498 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-2.1.dtd @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-default.xml b/xwork-core/src/main/resources/xwork-default.xml new file mode 100644 index 000000000..fdd8822ba --- /dev/null +++ b/xwork-core/src/main/resources/xwork-default.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-validator-1.0.2.dtd b/xwork-core/src/main/resources/xwork-validator-1.0.2.dtd new file mode 100644 index 000000000..18ced6c38 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-validator-1.0.2.dtd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-validator-1.0.3.dtd b/xwork-core/src/main/resources/xwork-validator-1.0.3.dtd new file mode 100644 index 000000000..582898c91 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-validator-1.0.3.dtd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-validator-1.0.dtd b/xwork-core/src/main/resources/xwork-validator-1.0.dtd new file mode 100644 index 000000000..69de370d3 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-validator-1.0.dtd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-validator-config-1.0.dtd b/xwork-core/src/main/resources/xwork-validator-config-1.0.dtd new file mode 100644 index 000000000..62dc72e99 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-validator-config-1.0.dtd @@ -0,0 +1,17 @@ + + + + + + + diff --git a/xwork-core/src/main/resources/xwork-validator-definition-1.0.dtd b/xwork-core/src/main/resources/xwork-validator-definition-1.0.dtd new file mode 100644 index 000000000..5b69a9501 --- /dev/null +++ b/xwork-core/src/main/resources/xwork-validator-definition-1.0.dtd @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/xwork-core/src/test/java/PackagelessAction.java b/xwork-core/src/test/java/PackagelessAction.java new file mode 100644 index 000000000..8c792c09f --- /dev/null +++ b/xwork-core/src/test/java/PackagelessAction.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import com.opensymphony.xwork2.ActionSupport; + + +/** + * @author Mark Woon + */ +public class PackagelessAction extends ActionSupport { + + /** + * Default constructor. + */ + public PackagelessAction() { + } + + + @Override + public String execute() { + // from action's bundle + System.out.println(getText("actionProperty")); + + // from default bundle + System.out.println(getText("foo.range")); + + // nonexistant + System.out.println(getText("non.existant")); + + return NONE; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ActionContextTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionContextTest.java new file mode 100644 index 000000000..dc7632f90 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionContextTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Unit test for {@link ActionContext}. + * + * @author Jason Carreira + */ +public class ActionContextTest extends XWorkTestCase { + + private static final String APPLICATION_KEY = "com.opensymphony.xwork2.ActionContextTest.application"; + private static final String SESSION_KEY = "com.opensymphony.xwork2.ActionContextTest.session"; + private static final String PARAMETERS_KEY = "com.opensymphony.xwork2.ActionContextTest.params"; + private static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContextTest.actionName"; + + private ActionContext context; + + @Override public void setUp() throws Exception { + super.setUp(); + ValueStack valueStack = container.getInstance(ValueStackFactory.class).createValueStack(); + Map extraContext = valueStack.getContext(); + Map application = new HashMap(); + application.put(APPLICATION_KEY, APPLICATION_KEY); + + Map session = new HashMap(); + session.put(SESSION_KEY, SESSION_KEY); + + Map params = new HashMap(); + params.put(PARAMETERS_KEY, PARAMETERS_KEY); + extraContext.put(ActionContext.APPLICATION, application); + extraContext.put(ActionContext.SESSION, session); + extraContext.put(ActionContext.PARAMETERS, params); + extraContext.put(ActionContext.ACTION_NAME, ACTION_NAME); + context = new ActionContext(extraContext); + ActionContext.setContext(context); + } + + public void testContextParams() { + assertTrue(ActionContext.getContext().getApplication().containsKey(APPLICATION_KEY)); + assertTrue(ActionContext.getContext().getSession().containsKey(SESSION_KEY)); + assertTrue(ActionContext.getContext().getParameters().containsKey(PARAMETERS_KEY)); + assertEquals(ActionContext.getContext().getName(), ACTION_NAME); + } + + public void testGetContext() { + ActionContext threadContext = ActionContext.getContext(); + assertEquals(context, threadContext); + } + + public void testNewActionContextCanFindDefaultTexts() { + ValueStack valueStack = context.getValueStack(); + String actionErrorMessage = (String) valueStack.findValue("getText('xwork.error.action.execution')"); + assertNotNull(actionErrorMessage); + assertEquals("Error during Action invocation", actionErrorMessage); + } + + public void testApplication() { + Map app = new HashMap(); + context.setApplication(app); + assertEquals(app, context.getApplication()); + } + + public void testContextMap() { + Map map = new HashMap(); + context.setContextMap(map); + assertEquals(map, context.getContextMap()); + } + + public void testParameters() { + Map param = new HashMap(); + context.setParameters(param); + assertEquals(param, context.getParameters()); + } + + public void testConversionErrors() { + Map errors = context.getConversionErrors(); + assertNotNull(errors); + assertEquals(0, errors.size()); + + Map errors2 = new HashMap(); + context.setConversionErrors(errors); + assertEquals(errors2, context.getConversionErrors()); + } + + public void testStaticMethods() { + assertEquals(context, ActionContext.getContext()); + + ActionContext context2 = new ActionContext(null); + ActionContext.setContext(context2); + + assertEquals(context2, ActionContext.getContext()); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ActionContextThreadLocalTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionContextThreadLocalTest.java new file mode 100644 index 000000000..d29aa3297 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionContextThreadLocalTest.java @@ -0,0 +1,42 @@ +/* + * 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 junit.framework.TestCase; + +import java.util.HashMap; + + +/** + * Simple Test ActionContext's ThreadLocal + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ActionContextThreadLocalTest extends TestCase { + + + public void testGetContext() throws Exception { + ActionContext.setContext(null); + assertNull(ActionContext.getContext()); + } + + public void testSetContext() throws Exception { + ActionContext context = new ActionContext(new HashMap()); + ActionContext.setContext(context); + assertEquals(context, ActionContext.getContext()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ActionInvocationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionInvocationTest.java new file mode 100644 index 000000000..b7c4aecf7 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionInvocationTest.java @@ -0,0 +1,101 @@ +/* + * 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; + +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; + + +/** + * @author $Author$ + * @version $Revision$ + */ +public class ActionInvocationTest extends XWorkTestCase { + + public void testCommandInvocation() throws Exception { + ActionProxy baseActionProxy = actionProxyFactory.createActionProxy( + "baz", "commandTest", null, null); + assertEquals("success", baseActionProxy.execute()); + + ActionProxy commandActionProxy = actionProxyFactory.createActionProxy( + "baz", "myCommand", null, null); + assertEquals(SimpleAction.COMMAND_RETURN_CODE, commandActionProxy.execute()); + } + + public void testCommandInvocationDoMethod() throws Exception { + ActionProxy baseActionProxy = actionProxyFactory.createActionProxy( + "baz", "doMethodTest", null, null); + assertEquals("input", baseActionProxy.execute()); + } + + public void testCommandInvocationUnknownHandler() throws Exception { + + DefaultActionProxy baseActionProxy = (DefaultActionProxy) actionProxyFactory.createActionProxy( + "baz", "unknownMethodTest", "unknownmethod", null); + UnknownHandler unknownHandler = new UnknownHandler() { + public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException { return null;} + public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) throws XWorkException { + return null; + } + public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException { + if (methodName.equals("unknownmethod")) { + return "found"; + } else { + return null; + } + } + }; + + UnknownHandlerManagerMock uhm = new UnknownHandlerManagerMock(); + uhm.addUnknownHandler(unknownHandler); + ((DefaultActionInvocation)baseActionProxy.getInvocation()).setUnknownHandlerManager(uhm); + + assertEquals("found", baseActionProxy.execute()); + } + + public void testResultReturnInvocationAndWired() throws Exception { + ActionProxy baseActionProxy = actionProxyFactory.createActionProxy( + "baz", "resultAction", null, null); + assertEquals(null, baseActionProxy.execute()); + assertTrue(SimpleAction.resultCalled); + } + + public void testSimple() { + HashMap params = new HashMap(); + params.put("blah", "this is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy( "", "Foo", null, extraContext); + proxy.execute(); + assertEquals("this is blah", proxy.getInvocation().getStack().findValue("[1].blah")); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ActionNestingTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionNestingTest.java new file mode 100644 index 000000000..ff8b8ed1b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionNestingTest.java @@ -0,0 +1,143 @@ +/* + * 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; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.mock.MockResult; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +import java.util.HashMap; + + +/** + * ActionNestingTest + * + * @author Jason Carreira + * Created Mar 5, 2003 2:02:01 PM + */ +public class ActionNestingTest extends XWorkTestCase { + + public static final String VALUE = "myValue"; + public static final String NESTED_VALUE = "myNestedValue"; + public static final String KEY = "myProperty"; + public static final String NESTED_KEY = "nestedProperty"; + public static final String NAMESPACE = "NestedActionTest"; + public static final String SIMPLE_ACTION_NAME = "SimpleAction"; + public static final String NO_STACK_ACTION_NAME = "NoStackNestedAction"; + public static final String STACK_ACTION_NAME = "StackNestedAction"; + + + private ActionContext context; + + + public String getMyProperty() { + return VALUE; + } + + @Override public void setUp() throws Exception { + super.setUp(); + loadConfigurationProviders(new NestedTestConfigurationProvider()); + + context = ActionContext.getContext(); + context.getValueStack().push(this); + } + + @Override protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testNestedContext() throws Exception { + assertEquals(context, ActionContext.getContext()); + ActionProxy proxy = actionProxyFactory.createActionProxy(NAMESPACE, SIMPLE_ACTION_NAME, null); + proxy.execute(); + assertEquals(context, ActionContext.getContext()); + } + + public void testNestedNoValueStack() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + assertEquals(VALUE, stack.findValue(KEY)); + + ActionProxy proxy = actionProxyFactory.createActionProxy(NAMESPACE, NO_STACK_ACTION_NAME, null); + proxy.execute(); + stack = ActionContext.getContext().getValueStack(); + assertEquals(stack.findValue(KEY), VALUE); + assertNull(stack.findValue(NESTED_KEY)); + } + + public void testNestedValueStack() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + assertEquals(VALUE, stack.findValue(KEY)); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.VALUE_STACK, stack); + + ActionProxy proxy = actionProxyFactory.createActionProxy(NAMESPACE, STACK_ACTION_NAME, extraContext); + proxy.execute(); + assertEquals(context, ActionContext.getContext()); + assertEquals(stack, ActionContext.getContext().getValueStack()); + assertEquals(VALUE, stack.findValue(KEY)); + assertEquals(NESTED_VALUE, stack.findValue(NESTED_KEY)); + assertEquals(3, stack.size()); + } + + + class NestedTestConfigurationProvider implements ConfigurationProvider { + private Configuration configuration; + public void destroy() { + } + public void init(Configuration configuration) { + this.configuration = configuration; + } + + public void register(ContainerBuilder builder, LocatableProperties props) { + } + + public void loadPackages() { + + PackageConfig packageContext = new PackageConfig.Builder("nestedActionTest") + .addActionConfig(SIMPLE_ACTION_NAME, new ActionConfig.Builder("nestedActionTest", SIMPLE_ACTION_NAME, SimpleAction.class.getName()) + .addResultConfig(new ResultConfig.Builder(Action.SUCCESS, MockResult.class.getName()).build()) + .addResultConfig(new ResultConfig.Builder(Action.ERROR, MockResult.class.getName()).build()) + .build()) + .addActionConfig(NO_STACK_ACTION_NAME, new ActionConfig.Builder("nestedActionTest", NO_STACK_ACTION_NAME, NestedAction.class.getName()) + .addResultConfig(new ResultConfig.Builder(Action.SUCCESS, MockResult.class.getName()).build()) + .methodName("noStack") + .build()) + .addActionConfig(STACK_ACTION_NAME, new ActionConfig.Builder("nestedActionTest", STACK_ACTION_NAME, NestedAction.class.getName()) + .addResultConfig(new ResultConfig.Builder(Action.SUCCESS, MockResult.class.getName()).build()) + .methodName("stack") + .build()) + .namespace(NAMESPACE) + .build(); + configuration.addPackageConfig("nestedActionTest", packageContext); + } + + /** + * Tells whether the ConfigurationProvider should reload its configuration + * + * @return + */ + public boolean needsReload() { + return false; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ActionSupportTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionSupportTest.java new file mode 100644 index 000000000..aec6acb6f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ActionSupportTest.java @@ -0,0 +1,330 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Unit test for {@link ActionSupport}. + * + * @author Claus Ibsen + */ +public class ActionSupportTest extends XWorkTestCase { + + private ActionSupport as; + + @Override + protected void setUp() throws Exception { + super.setUp(); + as = new ActionSupport(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + as = null; + } + + public void testNothingDoneOnActionSupport() throws Exception { + assertEquals(false, as.hasErrors()); + + assertNotNull(as.getActionErrors()); + assertEquals(0, as.getActionErrors().size()); + assertEquals(false, as.hasActionErrors()); + + assertNotNull(as.getActionMessages()); + assertEquals(0, as.getActionMessages().size()); + assertEquals(false, as.hasActionMessages()); + + assertNotNull(as.getFieldErrors()); + assertEquals(0, as.getFieldErrors().size()); + assertEquals(false, as.hasFieldErrors()); + + assertNull(as.getText(null)); + + try { + as.pause(null); + } catch (Exception e) { + fail("Should not fail"); + } + + assertEquals(Action.INPUT, as.input()); + assertEquals(Action.SUCCESS, as.doDefault()); + assertEquals(Action.SUCCESS, as.execute()); + try { + as.clone(); + fail("Failure expected for clone()"); + } catch (CloneNotSupportedException e) { + // success! + } + + + assertNull(as.getText(null, (List) null)); + assertNull(as.getText(null, (String) null)); + assertNull(as.getText(null, (String[]) null)); + + assertNull(as.getText(null, (String) null, (List) null)); + assertNull(as.getText(null, (String) null, (String) null)); + assertNull(as.getText(null, (String) null, (String[]) null)); + + assertNull(as.getText(null, (String) null, (List) null, (ValueStack) null)); + assertNull(as.getText(null, (String) null, (String[]) null, (ValueStack) null)); + + assertNotNull(as.getLocale()); + assertEquals(ActionContext.getContext().getLocale(), as.getLocale()); + + assertNull(as.getTexts()); // can not find a bundle + assertEquals("not.in.bundle", as.getText("not.in.bundle")); + } + + public void testActionErrors() { + assertEquals(false, as.hasActionErrors()); + assertEquals(0, as.getActionErrors().size()); + as.addActionError("Damm"); + assertEquals(1, as.getActionErrors().size()); + assertEquals("Damm", as.getActionErrors().iterator().next()); + assertEquals(true, as.hasActionErrors()); + assertEquals(true, as.hasErrors()); + + as.clearErrorsAndMessages(); + assertEquals(false, as.hasActionErrors()); + assertEquals(false, as.hasErrors()); + } + + public void testActionMessages() { + assertEquals(false, as.hasActionMessages()); + assertEquals(0, as.getActionMessages().size()); + as.addActionMessage("Killroy was here"); + assertEquals(1, as.getActionMessages().size()); + assertEquals("Killroy was here", as.getActionMessages().iterator().next()); + assertEquals(true, as.hasActionMessages()); + + assertEquals(false, as.hasActionErrors()); // does not count as a error + assertEquals(false, as.hasErrors()); // does not count as a error + + as.clearErrorsAndMessages(); + assertEquals(false, as.hasActionMessages()); + assertEquals(false, as.hasErrors()); + } + + public void testFieldErrors() { + assertEquals(false, as.hasFieldErrors()); + assertEquals(0, as.getFieldErrors().size()); + as.addFieldError("username", "Admin is not allowed as username"); + List errors = as.getFieldErrors().get("username"); + assertEquals(1, errors.size()); + assertEquals("Admin is not allowed as username", errors.get(0)); + + assertEquals(true, as.hasFieldErrors()); + assertEquals(true, as.hasErrors()); + + as.clearErrorsAndMessages(); + assertEquals(false, as.hasFieldErrors()); + assertEquals(false, as.hasErrors()); + } + + public void testDeprecated() throws Exception { + assertNotNull(as.getErrorMessages()); + assertEquals(0, as.getErrorMessages().size()); + + assertNotNull(as.getErrors()); + assertEquals(0, as.getErrors().size()); + } + + public void testLocale() { + Locale defLocale = Locale.getDefault(); + ActionContext.getContext().setLocale(null); + + // will never return null, if no locale is set then default is returned + assertNotNull(as.getLocale()); + assertEquals(defLocale, as.getLocale()); + + ActionContext.getContext().setLocale(Locale.ITALY); + assertEquals(Locale.ITALY, as.getLocale()); + + ActionContext.setContext(new ActionContext(new HashMap())); + assertEquals(defLocale, as.getLocale()); // ActionContext will create a new context, when it was set to null before + } + + public void testMyActionSupport() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + assertEquals("santa", mas.doDefault()); + assertNotNull(mas.getTexts()); + + assertEquals(false, mas.hasActionMessages()); + mas.validate(); + assertEquals(true, mas.hasActionMessages()); + } + + public void testSimpleGetTexts() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + checkGetTexts(mas); + } + + public void testSimpleGetTextsWithInjectedTextProvider() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + TextProvider textProvider = container.getInstance(TextProvider.class, "system"); + + assertNotNull(textProvider); + + container.inject(mas); + + checkGetTexts(mas); + } + + private void checkGetTexts(MyActionSupport mas) { + assertEquals("Hello World", mas.getText("hello")); + assertEquals("not.in.bundle", mas.getText("not.in.bundle")); + + assertEquals("Hello World", mas.getText("hello", "this is default")); + assertEquals("this is default", mas.getText("not.in.bundle", "this is default")); + + List nullList = null; + assertEquals("Hello World", mas.getText("hello", nullList)); + + String[] nullStrings = null; + assertEquals("Hello World", mas.getText("hello", nullStrings)); + } + + public void testGetTextsWithArgs() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + assertEquals("Hello World", mas.getText("hello", "this is default", "from me")); // no args in bundle + assertEquals("Hello World from me", mas.getText("hello.0", "this is default", "from me")); + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", "from me")); + assertEquals("this is default from me", mas.getText("not.in.bundle", "this is default {0}", "from me")); + + assertEquals("not.in.bundle", mas.getText("not.in.bundle")); + } + + public void testGetTextsWithListArgs() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + List args = new ArrayList(); + args.add("Santa"); + args.add("loud"); + assertEquals("Hello World", mas.getText("hello", "this is default", args)); // no args in bundle + assertEquals("Hello World Santa", mas.getText("hello.0", "this is default", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", mas.getText("hello.1", "this is default", args)); + + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", args)); + assertEquals("this is default Santa", mas.getText("not.in.bundle", "this is default {0}", args)); + assertEquals("this is default Santa speaking loud", mas.getText("not.in.bundle", "this is default {0} speaking {1}", args)); + + assertEquals("Hello World", mas.getText("hello", args)); // no args in bundle + assertEquals("Hello World Santa", mas.getText("hello.0", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", mas.getText("hello.1", args)); + + assertEquals("not.in.bundle", mas.getText("not.in.bundle", args)); + + assertEquals("Hello World", mas.getText("hello", "this is default", (List) null)); + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", (List) null)); + } + + public void testGetTextsWithArrayArgs() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + String[] args = {"Santa", "loud"}; + assertEquals("Hello World", mas.getText("hello", "this is default", args)); // no args in bundle + assertEquals("Hello World Santa", mas.getText("hello.0", "this is default", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", mas.getText("hello.1", "this is default", args)); + + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", args)); + assertEquals("this is default Santa", mas.getText("not.in.bundle", "this is default {0}", args)); + assertEquals("this is default Santa speaking loud", mas.getText("not.in.bundle", "this is default {0} speaking {1}", args)); + + assertEquals("Hello World", mas.getText("hello", args)); // no args in bundle + assertEquals("Hello World Santa", mas.getText("hello.0", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", mas.getText("hello.1", args)); + + assertEquals("not.in.bundle", mas.getText("not.in.bundle", args)); + + assertEquals("Hello World", mas.getText("hello", "this is default", (String[]) null)); + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", (String[]) null)); + } + + public void testGetTextsWithListAndStack() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + ValueStack stack = ActionContext.getContext().getValueStack(); + + List args = new ArrayList(); + args.add("Santa"); + args.add("loud"); + assertEquals("Hello World", mas.getText("hello", "this is default", args, stack)); // no args in bundle + assertEquals("Hello World Santa", mas.getText("hello.0", "this is default", args, stack)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", mas.getText("hello.1", "this is default", args, stack)); + + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", args, stack)); + assertEquals("this is default Santa", mas.getText("not.in.bundle", "this is default {0}", args, stack)); + assertEquals("this is default Santa speaking loud", mas.getText("not.in.bundle", "this is default {0} speaking {1}", args, stack)); + } + + public void testGetTextsWithArrayAndStack() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + ValueStack stack = ActionContext.getContext().getValueStack(); + + String[] args = {"Santa", "loud"}; + assertEquals("Hello World", mas.getText("hello", "this is default", args, stack)); // no args in bundle + assertEquals("Hello World Santa", mas.getText("hello.0", "this is default", args, stack)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", mas.getText("hello.1", "this is default", args, stack)); + + assertEquals("this is default", mas.getText("not.in.bundle", "this is default", args, stack)); + assertEquals("this is default Santa", mas.getText("not.in.bundle", "this is default {0}", args, stack)); + assertEquals("this is default Santa speaking loud", mas.getText("not.in.bundle", "this is default {0} speaking {1}", args, stack)); + } + + public void testGetBundle() throws Exception { + ActionContext.getContext().setLocale(new Locale("da")); + MyActionSupport mas = new MyActionSupport(); + + ResourceBundle rb = ResourceBundle.getBundle(MyActionSupport.class.getName(), new Locale("da")); + assertEquals(rb, mas.getTexts(MyActionSupport.class.getName())); + } + + private class MyActionSupport extends ActionSupport { + + @Override + public String doDefault() throws Exception { + return "santa"; + } + + @Override + public void validate() { + super.validate(); // to have code coverage + addActionMessage("validation was called"); + } + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/AnnotatedTestBean.java b/xwork-core/src/test/java/com/opensymphony/xwork2/AnnotatedTestBean.java new file mode 100644 index 000000000..98a5d9611 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/AnnotatedTestBean.java @@ -0,0 +1,76 @@ +/* + * 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.validator.annotations.IntRangeFieldValidator; +import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator; +import com.opensymphony.xwork2.validator.annotations.Validations; + +import java.util.Date; + + +/** + * AnnotatedTestBean + * @author Jason Carreira + * @author Rainer Hermanns + * Created Aug 4, 2003 12:39:53 AM + */ +public class AnnotatedTestBean { + //~ Instance fields //////////////////////////////////////////////////////// + + private Date birth; + private String name; + private int count; + + //~ Constructors /////////////////////////////////////////////////////////// + + public AnnotatedTestBean() { + } + + //~ Methods //////////////////////////////////////////////////////////////// + + public void setBirth(Date birth) { + this.birth = birth; + } + + public Date getBirth() { + return birth; + } + + @Validations( + intRangeFields = { + @IntRangeFieldValidator(shortCircuit = true, min = "1", max="100", key="invalid.count", message = "Invalid Count!"), + @IntRangeFieldValidator(shortCircuit = true, min = "20", max="28", key="invalid.count.bad", message = "Smaller Invalid Count: ${count}") + } + + ) + public void setCount(int count) { + this.count = count; + } + + public int getCount() { + return count; + } + + @RequiredStringValidator(message = "You must enter a name.") + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java new file mode 100644 index 000000000..b6d8d30b5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java @@ -0,0 +1,144 @@ +/* + * 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. + */ +/* + * Created on 28/02/2004 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork2; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.util.ValueStack; +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + + +/** + * @author CameronBraid + */ +public class ChainResultTest extends XWorkTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + } + + public void testNamespaceAndActionExpressionEvaluation() throws Exception { + ActionChainResult result = new ActionChainResult(); + result.setActionName("${actionName}"); + result.setNamespace("${namespace}"); + + String expectedActionName = "testActionName"; + String expectedNamespace = "testNamespace"; + Map values = new HashMap(); + values.put("actionName", expectedActionName); + values.put("namespace", expectedNamespace); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(values); + + Mock actionProxyMock = new Mock(ActionProxy.class); + actionProxyMock.expect("execute"); + + ActionProxyFactory testActionProxyFactory = new NamespaceActionNameTestActionProxyFactory(expectedNamespace, expectedActionName, (ActionProxy) actionProxyMock.proxy()); + result.setActionProxyFactory(testActionProxyFactory); + try { + + ActionContext testContext = new ActionContext(stack.getContext()); + ActionContext.setContext(testContext); + result.execute(null); + actionProxyMock.verify(); + } finally { + ActionContext.setContext(null); + } + } + + public void testRecursiveChain() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "InfiniteRecursionChain", null); + + try { + proxy.execute(); + fail("did not detected repeated chain to an action"); + } catch (XWorkException e) { + } + } + + private class NamespaceActionNameTestActionProxyFactory implements ActionProxyFactory { + private ActionProxy returnVal; + private String expectedActionName; + private String expectedNamespace; + + public NamespaceActionNameTestActionProxyFactory(String expectedNamespace, String expectedActionName, ActionProxy returnVal) { + this.expectedNamespace = expectedNamespace; + this.expectedActionName = expectedActionName; + this.returnVal = returnVal; + } + + public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext) { + TestCase.assertEquals(expectedNamespace, namespace); + TestCase.assertEquals(expectedActionName, actionName); + + return returnVal; + } + + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext) { + TestCase.assertEquals(expectedNamespace, namespace); + TestCase.assertEquals(expectedActionName, actionName); + + return returnVal; + } + + public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext, boolean executeResult, boolean cleanupContext) { + TestCase.assertEquals(expectedNamespace, namespace); + TestCase.assertEquals(expectedActionName, actionName); + + return returnVal; + } + + public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext) { + TestCase.assertEquals(expectedNamespace, namespace); + TestCase.assertEquals(expectedActionName, actionName); + + return returnVal; + } + + public ActionProxy createActionProxy(ActionInvocation actionInvocation, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { + TestCase.assertEquals(expectedNamespace, namespace); + TestCase.assertEquals(expectedActionName, actionName); + + return returnVal; + } + + public ActionProxy createActionProxy(String namespace, String actionName, String method, boolean executeResult, boolean cleanupContext) { + TestCase.assertEquals(expectedNamespace, namespace); + TestCase.assertEquals(expectedActionName, actionName); + + return returnVal; + } + + public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, + Map extraContext, boolean executeResult, boolean cleanupContext) throws Exception { + return null; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/CompositeTextProviderTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/CompositeTextProviderTest.java new file mode 100644 index 000000000..57788987e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/CompositeTextProviderTest.java @@ -0,0 +1,102 @@ +package com.opensymphony.xwork2; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * CompositeTextProviderTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class CompositeTextProviderTest extends XWorkTestCase { + + + private CompositeTextProvider textProvider = null; + + + public void testGetText() throws Exception { + // we should get the text from the 1st text provider + assertEquals(textProvider.getText("name"), "1 name"); + assertEquals(textProvider.getText("age"), "1 age"); + assertEquals(textProvider.getText("dog"), "This is a dog"); + assertEquals(textProvider.getText("cat"), "This is a cat"); + assertEquals(textProvider.getText("car"), "This is a car"); + assertEquals(textProvider.getText("bike"), "This is a bike"); + assertEquals(textProvider.getText("someNonExistingKey"), "someNonExistingKey"); + } + + + public void testGetTextWithDefaultValues() throws Exception { + assertEquals(textProvider.getText("name", "some default name"), "1 name"); + assertEquals(textProvider.getText("age", "some default age"), "1 age"); + assertEquals(textProvider.getText("no_such_key", "default value"), "default value"); + assertEquals(textProvider.getText("dog", "some default dog"), "This is a dog"); + assertEquals(textProvider.getText("cat", "some default cat"), "This is a cat"); + assertEquals(textProvider.getText("car", "some default car"), "This is a car"); + assertEquals(textProvider.getText("bike", "some default bike"), "This is a bike"); + } + + + public void testGetTextWithDefaultValuesAndArgs() throws Exception { + assertEquals(textProvider.getText("goodnight", "say good night", "Adam"), "1 good night Adam"); + assertEquals(textProvider.getText("goodnight", "say good night", new String[] { "Adam" }), "1 good night Adam"); + assertEquals(textProvider.getText("goodnight", "say good night", new ArrayList() { {add("Adam");} }), "1 good night Adam"); + assertEquals(textProvider.getText("goodmorning", "say good morning", new String[] { "Jack", "Jim" }), "1 good morning Jack and Jim"); + assertEquals(textProvider.getText("goodmorning", "say good morning", new ArrayList() { { add("Jack"); add("Jim"); }}), "1 good morning Jack and Jim"); + } + + public void testHasKey() throws Exception { + assertTrue(textProvider.hasKey("name")); + assertTrue(textProvider.hasKey("age")); + assertTrue(textProvider.hasKey("cat")); + assertTrue(textProvider.hasKey("dog")); + assertTrue(textProvider.hasKey("car")); + assertTrue(textProvider.hasKey("bike")); + assertTrue(textProvider.hasKey("goodnight")); + assertTrue(textProvider.hasKey("goodmorning")); + assertFalse(textProvider.hasKey("nosuchkey")); + } + + public void testGetResourceBundleByName() throws Exception { + assertNotNull(textProvider.getTexts("com.opensymphony.xwork2.validator.CompositeTextProviderTestResourceBundle1")); + assertNotNull(textProvider.getTexts("com.opensymphony.xwork2.validator.CompositeTextProviderTestResourceBundle2")); + assertNull(textProvider.getTexts("com.opensymphony.xwork2.validator.CompositeTextProviderTestResourceBundle3")); + } + + public void testGetResourceBundle() throws Exception { + assertNotNull(textProvider.getTexts()); + // we should get the first resource bundle where 'car' and 'bike' has a i18n msg + assertNotNull(textProvider.getTexts().getString("car")); + assertNotNull(textProvider.getTexts().getString("bike")); + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + textProvider = new CompositeTextProvider(new TextProvider[] { + new TextProviderSupport(ResourceBundle.getBundle("com.opensymphony.xwork2.validator.CompositeTextProviderTestResourceBundle1"), + new LocaleProvider() { + public Locale getLocale() { + return Locale.ENGLISH; + } + }), + new TextProviderSupport(ResourceBundle.getBundle("com.opensymphony.xwork2.validator.CompositeTextProviderTestResourceBundle2"), + new LocaleProvider() { + public Locale getLocale() { + return Locale.ENGLISH; + } + }) + + }); + } + + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + textProvider = null; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java new file mode 100644 index 000000000..08f2d750c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java @@ -0,0 +1,59 @@ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.mock.MockActionProxy; +import com.opensymphony.xwork2.mock.MockInterceptor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + + +/** + * A partial test of DefaultActionInvocation. + * Created to change interceptor chain logic. + * + * @author Kristian Rosenvold + */ +public class DefaultActionInvocationTest extends XWorkTestCase { + + /** + * Tests interceptor chain invoke. + * + * @throws Exception when action throws exception + */ + public void testInvoke() throws Exception { + List interceptorMappings = new ArrayList(); + MockInterceptor mockInterceptor1 = new MockInterceptor(); + mockInterceptor1.setFoo("test1"); + mockInterceptor1.setExpectedFoo("test1"); + interceptorMappings.add(new InterceptorMapping("test1", mockInterceptor1)); + MockInterceptor mockInterceptor2 = new MockInterceptor(); + interceptorMappings.add(new InterceptorMapping("test2", mockInterceptor2)); + mockInterceptor2.setFoo("test2"); + mockInterceptor2.setExpectedFoo("test2"); + MockInterceptor mockInterceptor3 = new MockInterceptor(); + interceptorMappings.add(new InterceptorMapping("test3", mockInterceptor3)); + mockInterceptor3.setFoo("test3"); + mockInterceptor3.setExpectedFoo("test3"); + + DefaultActionInvocation defaultActionInvocation = new DefaultActionInvocationTester(interceptorMappings); + defaultActionInvocation.invoke(); + assertTrue(mockInterceptor1.isExecuted()); + assertTrue(mockInterceptor2.isExecuted()); + assertTrue(mockInterceptor3.isExecuted()); + } + + + class DefaultActionInvocationTester extends DefaultActionInvocation { + DefaultActionInvocationTester(List interceptorMappings) { + super(new HashMap(), false); + interceptors = interceptorMappings.iterator(); + MockActionProxy actionProxy = new MockActionProxy(); + actionProxy.setMethod("execute"); + proxy = actionProxy; + action = new ActionSupport(); + } + } + +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultClasstTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultClasstTest.java new file mode 100644 index 000000000..c190d2508 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultClasstTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-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.providers.XmlConfigurationProvider; + +/** + * WildCardResultTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class DefaultClasstTest extends XWorkTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + } + + public void testWildCardEvaluation() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("Abstract-crud", "edit", null); + assertEquals("com.opensymphony.xwork2.SimpleAction", proxy.getConfig().getClassName()); + + proxy = actionProxyFactory.createActionProxy("/example", "edit", null); + assertEquals("com.opensymphony.xwork2.ModelDrivenAction", proxy.getConfig().getClassName()); + + + proxy = actionProxyFactory.createActionProxy("/example2", "override", null); + assertEquals("com.opensymphony.xwork2.ModelDrivenAction", proxy.getConfig().getClassName()); + + proxy = actionProxyFactory.createActionProxy("/example2/subItem", "save", null); + assertEquals("com.opensymphony.xwork2.ModelDrivenAction", proxy.getConfig().getClassName()); + + proxy = actionProxyFactory.createActionProxy("/example2", "list", null); + assertEquals("com.opensymphony.xwork2.ModelDrivenAction", proxy.getConfig().getClassName()); + + proxy = actionProxyFactory.createActionProxy("/example3", "list", null); + assertEquals("com.opensymphony.xwork2.SimpleAction", proxy.getConfig().getClassName()); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultTextProviderTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultTextProviderTest.java new file mode 100644 index 000000000..509a7e755 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/DefaultTextProviderTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import junit.framework.TestCase; + +import java.util.*; + +/** + * Unit test for {@link DefaultTextProvider}. + * + * @author Claus Ibsen + */ +public class DefaultTextProviderTest extends TestCase { + + private DefaultTextProvider tp; + + public void testSimpleGetTexts() throws Exception { + assertEquals("Hello World", tp.getText("hello")); + assertEquals(null, tp.getText("not.in.bundle")); + + assertEquals("Hello World", tp.getText("hello", "this is default")); + assertEquals("this is default", tp.getText("not.in.bundle", "this is default")); + + List nullList = null; + assertEquals("Hello World", tp.getText("hello", nullList)); + + String[] nullStrings = null; + assertEquals("Hello World", tp.getText("hello", nullStrings)); + } + + public void testGetTextsWithArgs() throws Exception { + assertEquals("Hello World", tp.getText("hello", "this is default", "from me")); // no args in bundle + assertEquals("Hello World from me", tp.getText("hello.0", "this is default", "from me")); + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", "from me")); + assertEquals("this is default from me", tp.getText("not.in.bundle", "this is default {0}", "from me")); + + assertEquals(null, tp.getText("not.in.bundle")); + } + + public void testGetTextsWithListArgs() throws Exception { + List args = new ArrayList(); + args.add("Santa"); + args.add("loud"); + assertEquals("Hello World", tp.getText("hello", "this is default", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", "this is default", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", "this is default", args)); + + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", args)); + assertEquals("this is default Santa", tp.getText("not.in.bundle", "this is default {0}", args)); + assertEquals("this is default Santa speaking loud", tp.getText("not.in.bundle", "this is default {0} speaking {1}", args)); + + assertEquals("Hello World", tp.getText("hello", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", args)); + + assertEquals(null, tp.getText("not.in.bundle", args)); + + assertEquals("Hello World", tp.getText("hello", "this is default", (List) null)); + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", (List) null)); + } + + public void testGetTextsWithArrayArgs() throws Exception { + String[] args = { "Santa", "loud" }; + assertEquals("Hello World", tp.getText("hello", "this is default", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", "this is default", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", "this is default", args)); + + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", args)); + assertEquals("this is default Santa", tp.getText("not.in.bundle", "this is default {0}", args)); + assertEquals("this is default Santa speaking loud", tp.getText("not.in.bundle", "this is default {0} speaking {1}", args)); + + assertEquals("Hello World", tp.getText("hello", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", args)); + + assertEquals(null, tp.getText("not.in.bundle", args)); + + assertEquals("Hello World", tp.getText("hello", "this is default", (String[]) null)); + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", (String[]) null)); + } + + public void testGetTextsWithListAndStack() throws Exception { + List args = new ArrayList(); + args.add("Santa"); + args.add("loud"); + assertEquals("Hello World", tp.getText("hello", "this is default", args, null)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", "this is default", args, null)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", "this is default", args, null)); + + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", args, null)); + assertEquals("this is default Santa", tp.getText("not.in.bundle", "this is default {0}", args, null)); + assertEquals("this is default Santa speaking loud", tp.getText("not.in.bundle", "this is default {0} speaking {1}", args, null)); + } + + public void testGetTextsWithArrayAndStack() throws Exception { + String[] args = { "Santa", "loud" }; + assertEquals("Hello World", tp.getText("hello", "this is default", args, null)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", "this is default", args, null)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", "this is default", args, null)); + + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", args, null)); + assertEquals("this is default Santa", tp.getText("not.in.bundle", "this is default {0}", args, null)); + assertEquals("this is default Santa speaking loud", tp.getText("not.in.bundle", "this is default {0} speaking {1}", args, null)); + } + + public void testGetBundle() throws Exception { + assertNull(tp.getTexts()); // always returns null + + ResourceBundle rb = ResourceBundle.getBundle(TextProviderSupportTest.class.getName(), Locale.CANADA); + assertEquals(rb, tp.getTexts(TextProviderSupportTest.class.getName())); + } + + @Override + protected void setUp() throws Exception { + ActionContext ctx = new ActionContext(new HashMap()); + ActionContext.setContext(ctx); + ctx.setLocale(Locale.CANADA); + + LocalizedTextUtil.clearDefaultResourceBundles(); + LocalizedTextUtil.addDefaultResourceBundle(DefaultTextProviderTest.class.getName()); + + tp = new DefaultTextProvider(); + } + + @Override + protected void tearDown() throws Exception { + ActionContext.setContext(null); + tp = null; + } + + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ExternalReferenceAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ExternalReferenceAction.java new file mode 100644 index 000000000..ecf2ceb75 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ExternalReferenceAction.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to Window - Preferences - + * Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork2; + + +/** + * @author Mike + *

+ * To change the template for this generated type comment go to Window - + * Preferences - Java - Code Generation - Code and Comments + */ +public class ExternalReferenceAction implements Action { + + private Foo foo; + + + /** + * @param foo The foo to set. + */ + public void setFoo(Foo foo) { + this.foo = foo; + } + + /** + * @return Returns the foo. + */ + public Foo getFoo() { + return foo; + } + + public String execute() throws Exception { + return SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/Foo.java b/xwork-core/src/test/java/com/opensymphony/xwork2/Foo.java new file mode 100644 index 000000000..25d5bb067 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/Foo.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork2; + + +/** + * @author Mike + *

+ * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class Foo { + + String name = null; + + + public Foo() { + name = "not set"; + } + + public Foo(String name) { + this.name = name; + } + + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/GenericsBean.java b/xwork-core/src/test/java/com/opensymphony/xwork2/GenericsBean.java new file mode 100644 index 000000000..9ea3f2d8d --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/GenericsBean.java @@ -0,0 +1,57 @@ +package com.opensymphony.xwork2; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * GenericsBean + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class GenericsBean { + private List blubb; + private List getterList; + private Map genericMap = new HashMap(); + private Map> extendedMap = new HashMap>(); + + /** + * @return Returns the doubles. + */ + public List getDoubles() { + return blubb; + } + + /** + * @param doubles The doubles to set. + */ + public void setDoubles(List doubles) { + this.blubb = doubles; + } + + public Map getGenericMap() { + return genericMap; + } + + public void setGenericMap(Map genericMap) { + this.genericMap = genericMap; + } + + public List getGetterList() { + if ( getterList == null ) { + getterList = new ArrayList(1); + getterList.add(42.42); + } + return getterList; + } + + public Map> getExtendedMap() { + return extendedMap; + } + + public void setExtendedMap(Map> extendedMap) { + this.extendedMap = extendedMap; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/LocaleAwareTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/LocaleAwareTest.java new file mode 100644 index 000000000..463501336 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/LocaleAwareTest.java @@ -0,0 +1,68 @@ +/* + * 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; + +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; + +import java.util.Locale; + + +/** + * LocaleAwareTest + * + * @author Jason Carreira + * Created Feb 10, 2003 6:13:13 PM + */ +public class LocaleAwareTest extends XWorkTestCase { + + public void testGetText() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.FOO_ACTION_NAME, null); + ActionContext.getContext().setLocale(Locale.US); + + TextProvider localeAware = (TextProvider) proxy.getAction(); + assertEquals("Foo Range Message", localeAware.getText("foo.range")); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testLocaleGetText() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.FOO_ACTION_NAME, null); + ActionContext.getContext().setLocale(Locale.GERMANY); + + TextProvider localeAware = (TextProvider) proxy.getAction(); + assertEquals("I don't know German", localeAware.getText("foo.range")); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override + protected void setUp() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider()); + + ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack(); + stack.getContext().put(ActionContext.CONTAINER, container); + ActionContext.setContext(new ActionContext(stack.getContext())); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ModelDrivenAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ModelDrivenAction.java new file mode 100644 index 000000000..525c34bc5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ModelDrivenAction.java @@ -0,0 +1,45 @@ +/* + * 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; + + +/** + * ModelDrivenAction + * + * @author Jason Carreira + * Created Apr 8, 2003 6:27:29 PM + */ +public class ModelDrivenAction extends ActionSupport implements ModelDriven { + + private String foo; + private TestBean model = new TestBean(); + + + public void setFoo(String foo) { + this.foo = foo; + } + + public String getFoo() { + return foo; + } + + /** + * @return the model to be pushed onto the ValueStack after the Action itself + */ + public Object getModel() { + return model; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ModelDrivenAnnotationAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ModelDrivenAnnotationAction.java new file mode 100644 index 000000000..279208c60 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ModelDrivenAnnotationAction.java @@ -0,0 +1,45 @@ +/* + * 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; + +/** + * ModelDrivenAnnotationAction + * + * @author Jason Carreira + * @author Rainer Hermanns + * Created Apr 8, 2003 6:27:29 PM + */ +public class ModelDrivenAnnotationAction extends ActionSupport implements ModelDriven { + + private String foo; + private AnnotatedTestBean model = new AnnotatedTestBean(); + + + public void setFoo(String foo) { + this.foo = foo; + } + + public String getFoo() { + return foo; + } + + /** + * @return the model to be pushed onto the ValueStack after the Action itself + */ + public Object getModel() { + return model; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/NestedAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/NestedAction.java new file mode 100644 index 000000000..5d743f266 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/NestedAction.java @@ -0,0 +1,67 @@ +/* + * 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; + +import com.opensymphony.xwork2.util.ValueStack; +import junit.framework.Assert; + + +/** + * NestedAction + * + * @author Jason Carreira + * Created Mar 5, 2003 3:08:19 PM + */ +public class NestedAction implements Action { + + private String nestedProperty = ActionNestingTest.NESTED_VALUE; + + + public NestedAction() { + } + + + public String getNestedProperty() { + return nestedProperty; + } + + public String execute() throws Exception { + Assert.fail(); + + return null; + } + + public String noStack() { + ValueStack stack = ActionContext.getContext().getValueStack(); + // Action + DefaultTextProvider on the stack + Assert.assertEquals(2, stack.size()); + Assert.assertNull(stack.findValue(ActionNestingTest.KEY)); + Assert.assertEquals(ActionNestingTest.NESTED_VALUE, stack.findValue(ActionNestingTest.NESTED_KEY)); + + return SUCCESS; + } + + public String stack() { + ValueStack stack = ActionContext.getContext().getValueStack(); + //DefaultTextProvider, NestedActionTest pushed on by the test, and the NestedAction + Assert.assertEquals(3, stack.size()); + Assert.assertNotNull(stack.findValue(ActionNestingTest.KEY)); + Assert.assertEquals(ActionContext.getContext().getValueStack().findValue(ActionNestingTest.KEY), ActionNestingTest.VALUE); + Assert.assertEquals(ActionNestingTest.NESTED_VALUE, stack.findValue(ActionNestingTest.NESTED_KEY)); + + return SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationAction.java new file mode 100644 index 000000000..60f8ae87e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationAction.java @@ -0,0 +1,10 @@ +package com.opensymphony.xwork2; + +/** + * Need by the ProxyInvocationTest + */ +public class ProxyInvocationAction extends ActionSupport implements ProxyInvocationInterface { + public String show() { + return "proxyResult"; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationInterface.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationInterface.java new file mode 100644 index 000000000..e548a78f1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationInterface.java @@ -0,0 +1,8 @@ +package com.opensymphony.xwork2; + +/** + * Need by the ProxyInvocationTest + */ +public interface ProxyInvocationInterface { + public String show(); +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationTest.java new file mode 100644 index 000000000..5238a6fa3 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyInvocationTest.java @@ -0,0 +1,49 @@ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; +import java.util.Map; + +/** + * Contribed by: Ruben Inoto + */ +public class ProxyInvocationTest extends XWorkTestCase { + + /** + * Sets a ProxyObjectFactory as ObjectFactory (so the FooAction will always be retrieved + * as a FooProxy), and it tries to call invokeAction on the TestActionInvocation. + * + * It should fail, because the Method got from the action (actually a FooProxy) + * will be executed on the InvocationHandler of the action (so, in the action itself). + */ + public void testProxyInvocation() throws Exception { + + ActionProxy proxy = actionProxyFactory + .createActionProxy("", "ProxyInvocation", createDummyContext()); + ActionInvocation invocation = proxy.getInvocation(); + + String result = invocation.invokeActionOnly(); + assertEquals("proxyResult", result); + + } + + /** + * Needed for the creation of the action proxy + */ + private Map createDummyContext() { + Map params = new HashMap(); + params.put("blah", "this is blah"); + Map extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + return extraContext; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-proxyinvoke.xml")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyObjectFactory.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyObjectFactory.java new file mode 100644 index 000000000..37821c8ad --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ProxyObjectFactory.java @@ -0,0 +1,46 @@ +package com.opensymphony.xwork2; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; + +/** + * ObjectFactory that returns a FooProxy in the buildBean if the clazz is FooAction + */ +public class ProxyObjectFactory extends ObjectFactory { + + /** + * It returns an instance of the bean except if the class is FooAction. + * In this case, it returns a FooProxy of it. + */ + @Override + public Object buildBean(Class clazz, Map extraContext) + throws Exception { + Object bean = super.buildBean(clazz, extraContext); + if(clazz.equals(ProxyInvocationAction.class)) { + return Proxy.newProxyInstance(bean.getClass() + .getClassLoader(), bean.getClass().getInterfaces(), + new ProxyInvocationProxy(bean)); + + } + return bean; + } + + /** + * Simple proxy that just invokes the method on the target on the invoke method + */ + public class ProxyInvocationProxy implements InvocationHandler { + + private Object target; + + public ProxyInvocationProxy(Object target) { + this.target = target; + } + + public Object invoke(Object proxy, Method m, Object[] args) + throws Throwable { + return m.invoke(target, args); + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java new file mode 100644 index 000000000..d22d231c1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAction.java @@ -0,0 +1,271 @@ +/* + * 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; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.inject.Inject; + +import java.util.*; + + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class SimpleAction extends ActionSupport { + + public static final String COMMAND_RETURN_CODE = "com.opensymphony.xwork2.SimpleAction.CommandInvoked"; + + + private ArrayList someList = new ArrayList(); + private Date date = new Date(); + private Properties settings = new Properties(); + private String blah; + private String name; + private TestBean bean = new TestBean(); + private boolean throwException; + private int bar; + private int baz; + private int foo; + private long longFoo; + private short shortFoo; + private double percentage; + private Map indexedProps = new HashMap(); + + private String aliasSource; + private String aliasDest; + private Map protectedMap = new HashMap(); + private Map existingMap = new HashMap(); + + public static boolean resultCalled; + + + public SimpleAction() { + resultCalled = false; + existingMap.put("existingKey", "value"); + } + + public Map getTheProtectedMap() { + return protectedMap; + } + + protected Map getTheSemiProtectedMap() { + return protectedMap; + } + + public void setExistingMap(Map map) { + this.existingMap = map; + } + + public Map getTheExistingMap() { + return existingMap; + } + + + public void setBar(int bar) { + this.bar = bar; + } + + public int getBar() { + return bar; + } + + public double getPercentage() { + return percentage; + } + + public void setPercentage(double percentage) { + this.percentage = percentage; + } + + public void setBaz(int baz) { + this.baz = baz; + } + + public int getBaz() { + return baz; + } + + public void setBean(TestBean bean) { + this.bean = bean; + } + + public TestBean getBean() { + return bean; + } + + public void setBlah(String blah) { + this.blah = blah; + } + + public String getBlah() { + return blah; + } + + public Boolean getBool(String b) { + return new Boolean(b); + } + + public boolean[] getBools() { + boolean[] b = new boolean[]{true, false, false, true}; + + return b; + } + + public void setDate(Date date) { + this.date = date; + } + + public Date getDate() { + return date; + } + + public void setFoo(int foo) { + this.foo = foo; + } + + public int getFoo() { + return foo; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setSettings(Properties settings) { + this.settings = settings; + } + + public Properties getSettings() { + return settings; + } + + + public String getAliasDest() { + return aliasDest; + } + + public void setAliasDest(String aliasDest) { + this.aliasDest = aliasDest; + } + + public String getAliasSource() { + return aliasSource; + } + + public void setAliasSource(String aliasSource) { + this.aliasSource = aliasSource; + } + + + public void setSomeList(ArrayList someList) { + this.someList = someList; + } + + public ArrayList getSomeList() { + return someList; + } + + public String getIndexedProp(int index) { + return indexedProps.get(index); + } + + public void setIndexedProp(int index, String val) { + indexedProps.put(index, val); + } + + + public void setThrowException(boolean throwException) { + this.throwException = throwException; + } + + public String commandMethod() throws Exception { + return COMMAND_RETURN_CODE; + } + + public Result resultAction() throws Exception { + return new Result() { + public Configuration configuration; + + @Inject + public void setConfiguration(Configuration config) { + this.configuration = config; + } + public void execute(ActionInvocation invocation) throws Exception { + if (configuration != null) + resultCalled = true; + } + + }; + } + + public String exceptionMethod() throws Exception { + if (throwException) { + throw new Exception("We're supposed to throw this"); + } + + return "OK"; + } + + @Override + public String execute() throws Exception { + if (foo == bar) { + return ERROR; + } + + baz = foo + bar; + + name = "HelloWorld"; + settings.put("foo", "bar"); + settings.put("black", "white"); + + someList.add("jack"); + someList.add("bill"); + someList.add("kerry"); + + return SUCCESS; + } + + public String doInput() throws Exception { + return INPUT; + } + + + public long getLongFoo() { + return longFoo; + } + + + public void setLongFoo(long longFoo) { + this.longFoo = longFoo; + } + + + public short getShortFoo() { + return shortFoo; + } + + + public void setShortFoo(short shortFoo) { + this.shortFoo = shortFoo; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAnnotationAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAnnotationAction.java new file mode 100644 index 000000000..79df77d5e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleAnnotationAction.java @@ -0,0 +1,231 @@ +/* + * 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.validator.annotations.*; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Properties; + + +/** + * Simple Test Action for annotaton processing. + * + * @author Rainer Hermanns + * @version $Revision$ + */ +@Validation() +public class SimpleAnnotationAction extends ActionSupport { + //~ Static fields/initializers ///////////////////////////////////////////// + + public static final String COMMAND_RETURN_CODE = "com.opensymphony.xwork2.SimpleAnnotationAction.CommandInvoked"; + + //~ Instance fields //////////////////////////////////////////////////////// + + private ArrayList someList = new ArrayList(); + private Date date = new Date(); + private Properties settings = new Properties(); + private String blah; + private String name; + private AnnotatedTestBean bean = new AnnotatedTestBean(); + private boolean throwException; + private int bar; + private int baz; + private int foo; + private double percentage; + + private String aliasSource; + private String aliasDest; + + + + //~ Constructors /////////////////////////////////////////////////////////// + + public SimpleAnnotationAction() { + } + + //~ Methods //////////////////////////////////////////////////////////////// + + @RequiredFieldValidator(type = ValidatorType.FIELD, message = "You must enter a value for bar.") + @IntRangeFieldValidator(type = ValidatorType.FIELD, min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.") + public void setBar(int bar) { + this.bar = bar; + } + + public int getBar() { + return bar; + } + + @IntRangeFieldValidator(min = "0", key = "baz.range", message = "Could not find baz.range!") + public void setBaz(int baz) { + this.baz = baz; + } + + public int getBaz() { + return baz; + } + + public double getPercentage() { + return percentage; + } + + @DoubleRangeFieldValidator(minInclusive = "0.123", key = "baz.range", message = "Could not find percentage.range!") + public void setPercentage(double percentage) { + this.percentage = percentage; + } + + public void setBean(AnnotatedTestBean bean) { + this.bean = bean; + } + + public AnnotatedTestBean getBean() { + return bean; + } + + public void setBlah(String blah) { + this.blah = blah; + } + + public String getBlah() { + return blah; + } + + public Boolean getBool(String b) { + return new Boolean(b); + } + + public boolean[] getBools() { + return new boolean[] {true, false, false, true}; + } + + @DateRangeFieldValidator(min = "12/22/2002", max = "12/25/2002", message = "The date must be between 12-22-2002 and 12-25-2002.") + public void setDate(Date date) { + this.date = date; + } + + public Date getDate() { + return date; + } + + public void setFoo(int foo) { + this.foo = foo; + } + + public int getFoo() { + return foo; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setSettings(Properties settings) { + this.settings = settings; + } + + public Properties getSettings() { + return settings; + } + + + public String getAliasDest() { + return aliasDest; + } + + public void setAliasDest(String aliasDest) { + this.aliasDest = aliasDest; + } + + public String getAliasSource() { + return aliasSource; + } + + public void setAliasSource(String aliasSource) { + this.aliasSource = aliasSource; + } + + + public void setSomeList(ArrayList someList) { + this.someList = someList; + } + + public ArrayList getSomeList() { + return someList; + } + + public void setThrowException(boolean throwException) { + this.throwException = throwException; + } + + public String commandMethod() throws Exception { + return COMMAND_RETURN_CODE; + } + + public String exceptionMethod() throws Exception { + if (throwException) { + throw new Exception("We're supposed to throw this"); + } + + return "OK"; + } + + @Override + @Validations( + requiredFields = + {@RequiredFieldValidator(type = ValidatorType.SIMPLE, fieldName = "customfield", message = "You must enter a value for field.")}, + requiredStrings = + {@RequiredStringValidator(type = ValidatorType.SIMPLE, fieldName = "stringisrequired", message = "You must enter a value for string.")}, + emails = + { @EmailValidator(type = ValidatorType.SIMPLE, fieldName = "emailaddress", message = "You must enter a value for email.")}, + urls = + { @UrlValidator(type = ValidatorType.SIMPLE, fieldName = "hreflocation", message = "You must enter a value for email.")}, + stringLengthFields = + {@StringLengthFieldValidator(type = ValidatorType.SIMPLE, trim = true, minLength="10" , maxLength = "12", fieldName = "needstringlength", message = "You must enter a stringlength.")}, + intRangeFields = + { @IntRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "intfield", min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.")}, + dateRangeFields = + {@DateRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "datefield", min = "-1", max = "99", message = "bar must be between ${min} and ${max}, current value is ${bar}.")}, + expressions = { + @ExpressionValidator(expression = "foo > 1", message = "Foo must be greater than Bar 1. Foo = ${foo}, Bar = ${bar}."), + @ExpressionValidator(expression = "foo > 2", message = "Foo must be greater than Bar 2. Foo = ${foo}, Bar = ${bar}."), + @ExpressionValidator(expression = "foo > 3", message = "Foo must be greater than Bar 3. Foo = ${foo}, Bar = ${bar}."), + @ExpressionValidator(expression = "foo > 4", message = "Foo must be greater than Bar 4. Foo = ${foo}, Bar = ${bar}."), + @ExpressionValidator(expression = "foo > 5", message = "Foo must be greater than Bar 5. Foo = ${foo}, Bar = ${bar}.") + } + ) + public String execute() throws Exception { + if (foo == bar) { + return ERROR; + } + + baz = foo + bar; + + name = "HelloWorld"; + settings.put("foo", "bar"); + settings.put("black", "white"); + + someList.add("jack"); + someList.add("bill"); + someList.add("kerry"); + + return SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleFooAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleFooAction.java new file mode 100644 index 000000000..557109f47 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/SimpleFooAction.java @@ -0,0 +1,30 @@ +/* + * 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; + + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class SimpleFooAction implements Action { + + public String execute() throws Exception { + return SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/StubValueStack.java b/xwork-core/src/test/java/com/opensymphony/xwork2/StubValueStack.java new file mode 100644 index 000000000..eac731b0a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/StubValueStack.java @@ -0,0 +1,100 @@ +/* + * 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.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.HashMap; +import java.util.Map; + +/** + * Stub value stack for testing + */ +public class StubValueStack implements ValueStack { + Map ctx = new HashMap(); + CompoundRoot root = new CompoundRoot(); + + public Map getContext() { + return ctx; + } + + public void setDefaultType(Class defaultType) { + } + + public void setExprOverrides(Map overrides) { + } + + public Map getExprOverrides() { + return null; + } + + public CompoundRoot getRoot() { + return root; + } + + public void setValue(String expr, Object value) { + ctx.put(expr, value); + } + + public void setValue(String expr, Object value, boolean throwExceptionOnFailure) { + ctx.put(expr, value); + } + + public String findString(String expr) { + return (String) ctx.get(expr); + } + + public String findString(String expr, boolean throwExceptionOnFailure) { + return findString(expr, false); + } + + public Object findValue(String expr) { + return ctx.get(expr); + } + + public Object findValue(String expr, boolean throwExceptionOnFailure) { + return findValue(expr, false); + } + + public Object findValue(String expr, Class asType) { + return ctx.get(expr); + } + + public Object findValue(String expr, Class asType, boolean throwExceptionOnFailure) { + return findValue(expr, asType, false); + } + + public Object peek() { + return root.peek(); + } + + public Object pop() { + return root.pop(); + } + + public void push(Object o) { + root.push(o); + } + + public void set(String key, Object o) { + ctx.put(key, o); + } + + public int size() { + return root.size(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/TestBean.java b/xwork-core/src/test/java/com/opensymphony/xwork2/TestBean.java new file mode 100644 index 000000000..3be8e1894 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/TestBean.java @@ -0,0 +1,72 @@ +/* + * 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; + +import java.util.Date; + + +/** + * TestBean + * + * @author Jason Carreira + * Created Aug 4, 2003 12:39:53 AM + */ +public class TestBean { + + private Date birth; + private String name; + private int count; + + private TestChildBean child = new TestChildBean(); + + public TestBean() { + } + + + public void setBirth(Date birth) { + this.birth = birth; + } + + public Date getBirth() { + return birth; + } + + public void setCount(int count) { + this.count = count; + } + + public int getCount() { + return count; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + + public TestChildBean getChild() { + return child; + } + + + public void setChild(TestChildBean child) { + this.child = child; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/TestChildBean.java b/xwork-core/src/test/java/com/opensymphony/xwork2/TestChildBean.java new file mode 100644 index 000000000..4a016689c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/TestChildBean.java @@ -0,0 +1,62 @@ +/* + * 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; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + + +/** + * TestBean + */ +public class TestChildBean { + + private Date birth; + private String name; + private int count; + + + public TestChildBean() { + Calendar cal = new GregorianCalendar(1900, 01, 01); + setBirth(cal.getTime()); + } + + + public void setBirth(Date birth) { + this.birth = birth; + } + + public Date getBirth() { + return birth; + } + + public void setCount(int count) { + this.count = count; + } + + public int getCount() { + return count; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/TestInterceptor.java b/xwork-core/src/test/java/com/opensymphony/xwork2/TestInterceptor.java new file mode 100644 index 000000000..fb71a063a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/TestInterceptor.java @@ -0,0 +1,86 @@ +/* + * 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; + +import com.opensymphony.xwork2.interceptor.Interceptor; +import junit.framework.Assert; + + +/** + * TestInterceptor + * + * @author Jason Carreira + * Created Apr 21, 2003 9:04:06 PM + */ +public class TestInterceptor implements Interceptor { + + public static final String DEFAULT_FOO_VALUE = "fooDefault"; + + + private String expectedFoo = DEFAULT_FOO_VALUE; + private String foo = DEFAULT_FOO_VALUE; + private boolean executed = false; + + + public boolean isExecuted() { + return executed; + } + + public void setExpectedFoo(String expectedFoo) { + this.expectedFoo = expectedFoo; + } + + public String getExpectedFoo() { + return expectedFoo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + public String getFoo() { + return foo; + } + + /** + * Called to let an interceptor clean up any resources it has allocated. + */ + public void destroy() { + } + + /** + * Called after an Interceptor is created, but before any requests are processed using the intercept() methodName. This + * gives the Interceptor a chance to initialize any needed resources. + */ + public void init() { + } + + /** + * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the + * request by the DefaultActionInvocation or to short-circuit the processing and just return a String return code. + * + * @param invocation + * @return + * @throws Exception + */ + public String intercept(ActionInvocation invocation) throws Exception { + executed = true; + Assert.assertNotSame(DEFAULT_FOO_VALUE, foo); + Assert.assertEquals(expectedFoo, foo); + + return invocation.invoke(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/TestNGXWorkTestCaseTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/TestNGXWorkTestCaseTest.java new file mode 100644 index 000000000..7ab7dc221 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/TestNGXWorkTestCaseTest.java @@ -0,0 +1,52 @@ +/* + * 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.ConfigurationManager; +import junit.framework.TestCase; +import org.testng.TestListenerAdapter; +import org.testng.TestNG; +import org.testng.annotations.Test; + +public class TestNGXWorkTestCaseTest extends TestCase { + + public void testSimpleTest() throws Exception { + TestListenerAdapter tla = new TestListenerAdapter(); + TestNG testng = new TestNG(); + testng.setTestClasses(new Class[] { RunTest.class }); + testng.addListener(tla); + try { + testng.run(); + assertEquals(1, tla.getPassedTests().size()); + assertEquals(0, tla.getFailedTests().size()); + assertTrue(RunTest.ran); + assertNotNull(RunTest.mgr); + } finally { + RunTest.mgr = null; + } + } + + @Test + public static class RunTest extends TestNGXWorkTestCase { + public static boolean ran = false; + public static ConfigurationManager mgr; + + public void testRun() { + ran = true; + mgr = this.configurationManager; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/TextProviderSupportTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/TextProviderSupportTest.java new file mode 100644 index 000000000..2c0b377fc --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/TextProviderSupportTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Unit test for {@link TextProviderSupport}. + * + * @author Claus Ibsen + */ +public class TextProviderSupportTest extends XWorkTestCase { + + private TextProviderSupport tp; + private java.util.ResourceBundle rb; + + public void testHasKey() throws Exception { + assertTrue(tp.hasKey("hello")); + assertFalse(tp.hasKey("not.in.bundle")); + } + + public void testSimpleGetTexts() throws Exception { + assertEquals("Hello World", tp.getText("hello")); + assertEquals("not.in.bundle", tp.getText("not.in.bundle")); + + assertEquals("Hello World", tp.getText("hello", "this is default")); + assertEquals("this is default", tp.getText("not.in.bundle", "this is default")); + } + + public void testGetTextsWithArgs() throws Exception { + assertEquals("Hello World", tp.getText("hello", "this is default", "from me")); // no args in bundle + assertEquals("Hello World from me", tp.getText("hello.0", "this is default", "from me")); + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", "from me")); + assertEquals("this is default from me", tp.getText("not.in.bundle", "this is default {0}", "from me")); + + assertEquals("not.in.bundle", tp.getText("not.in.bundle")); + } + + public void testGetTextsWithListArgs() throws Exception { + List args = new ArrayList(); + args.add("Santa"); + args.add("loud"); + assertEquals("Hello World", tp.getText("hello", "this is default", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", "this is default", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", "this is default", args)); + + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", args)); + assertEquals("this is default Santa", tp.getText("not.in.bundle", "this is default {0}", args)); + assertEquals("this is default Santa speaking loud", tp.getText("not.in.bundle", "this is default {0} speaking {1}", args)); + + assertEquals("Hello World", tp.getText("hello", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", args)); + + assertEquals("not.in.bundle", tp.getText("not.in.bundle", args)); + } + + public void testGetTextsWithArrayArgs() throws Exception { + String[] args = { "Santa", "loud" }; + assertEquals("Hello World", tp.getText("hello", "this is default", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", "this is default", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", "this is default", args)); + + assertEquals("this is default", tp.getText("not.in.bundle", "this is default", args)); + assertEquals("this is default Santa", tp.getText("not.in.bundle", "this is default {0}", args)); + assertEquals("this is default Santa speaking loud", tp.getText("not.in.bundle", "this is default {0} speaking {1}", args)); + + assertEquals("Hello World", tp.getText("hello", args)); // no args in bundle + assertEquals("Hello World Santa", tp.getText("hello.0", args)); // only 1 arg in bundle + assertEquals("Hello World. This is Santa speaking loud", tp.getText("hello.1", args)); + + assertEquals("not.in.bundle", tp.getText("not.in.bundle", args)); + } + + public void testGetBundle() throws Exception { + assertEquals(rb, tp.getTexts()); + assertEquals(rb, tp.getTexts(TextProviderSupportTest.class.getName())); + } + + public void testDifficultSymbols1() { + String val= tp.getText("symbols1"); + assertEquals("\"=!@#$%^&*(){qwe}<>?:|}{[]\\';/.,<>`~'", val); + } + + public void testDifficultSymbols2() { + String val= tp.getText("symbols2"); + assertEquals("\"=!@#$%^&*()<>?:|[]\\';/.,<>`~'", val); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + rb = ResourceBundle.getBundle(TextProviderSupportTest.class.getName(), Locale.ENGLISH); + tp = new TextProviderSupport(rb, new LocaleProvider() { + public Locale getLocale() { + return Locale.ENGLISH; + } + }); + } + + @Override + protected void tearDown() throws Exception { + rb = null; + tp = null; + } + + +} + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/UnknownHandlerManagerMock.java b/xwork-core/src/test/java/com/opensymphony/xwork2/UnknownHandlerManagerMock.java new file mode 100644 index 000000000..8b6eb5aa3 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/UnknownHandlerManagerMock.java @@ -0,0 +1,17 @@ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.DefaultUnknownHandlerManager; + +import java.util.ArrayList; + +/* + * Utility class for testing DefaultUnknownHandlerManager, which does not allow to add + * UnknownHandlers directly + */ +public class UnknownHandlerManagerMock extends DefaultUnknownHandlerManager { + public void addUnknownHandler(UnknownHandler uh) { + if (this.unknownHandlers == null) + this.unknownHandlers = new ArrayList(); + this.unknownHandlers.add(uh); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/UserSpecifiedDefaultAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/UserSpecifiedDefaultAction.java new file mode 100644 index 000000000..a9345e1c2 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/UserSpecifiedDefaultAction.java @@ -0,0 +1,10 @@ +package com.opensymphony.xwork2; + +/** + * UserSpecifiedDefaultAction + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class UserSpecifiedDefaultAction extends ActionSupport { +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ValidationOrderAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ValidationOrderAction.java new file mode 100644 index 000000000..f287fddc1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ValidationOrderAction.java @@ -0,0 +1,189 @@ +/* + * 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; + +/** + * A sample action to test validation order. + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ValidationOrderAction extends ActionSupport { + + private String username; + private String password; + private String confirmPassword; + private String firstName; + private String lastName; + private String city; + private String province; + private String country; + private String postalCode; + private String email; + private String website; + private String passwordHint; + + + + @Override + public String execute() throws Exception { + return SUCCESS; + } + + + + public String getCity() { + return city; + } + + + + public void setCity(String city) { + this.city = city; + } + + + + public String getConfirmPassword() { + return confirmPassword; + } + + + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + + + public String getCountry() { + return country; + } + + + + public void setCountry(String country) { + this.country = country; + } + + + + public String getEmail() { + return email; + } + + + + public void setEmail(String email) { + this.email = email; + } + + + + public String getFirstName() { + return firstName; + } + + + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + + + public String getLastName() { + return lastName; + } + + + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + + + public String getPassword() { + return password; + } + + + + public void setPassword(String password) { + this.password = password; + } + + + + public String getPasswordHint() { + return passwordHint; + } + + + + public void setPasswordHint(String passwordHint) { + this.passwordHint = passwordHint; + } + + + + public String getPostalCode() { + return postalCode; + } + + + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + + + public String getProvince() { + return province; + } + + + + public void setProvince(String province) { + this.province = province; + } + + + + public String getUsername() { + return username; + } + + + + public void setUsername(String username) { + this.username = username; + } + + + + public String getWebsite() { + return website; + } + + + + public void setWebsite(String website) { + this.website = website; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/VoidResult.java b/xwork-core/src/test/java/com/opensymphony/xwork2/VoidResult.java new file mode 100644 index 000000000..afc3f4f76 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/VoidResult.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + */ +public class VoidResult implements Result { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof VoidResult)) { + return false; + } + + return true; + } + + public void execute(ActionInvocation invocation) throws Exception { + } + + @Override + public int hashCode() { + return 42; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/WildCardResultTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/WildCardResultTest.java new file mode 100644 index 000000000..b4c0d6dd6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/WildCardResultTest.java @@ -0,0 +1,55 @@ +/* + * 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.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.mock.MockResult; + +/** + * WildCardResultTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class WildCardResultTest extends XWorkTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + } + + public void testWildCardEvaluation() throws Exception { + ActionContext.setContext(null); + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "WildCard", null); + assertEquals("success", proxy.execute()); + assertEquals(VoidResult.class, proxy.getInvocation().getResult().getClass()); + + ActionContext.setContext(null); + proxy = actionProxyFactory.createActionProxy(null, "WildCardInput", null); + assertEquals("input", proxy.execute()); + assertEquals(MockResult.class, proxy.getInvocation().getResult().getClass()); + + ActionContext.setContext(null); + proxy = actionProxyFactory.createActionProxy(null, "WildCardError", null); + assertEquals("error", proxy.execute()); + assertEquals(MockResult.class, proxy.getInvocation().getResult().getClass()); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/XWorkExceptionTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/XWorkExceptionTest.java new file mode 100644 index 000000000..787d6e5d3 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/XWorkExceptionTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2; + +import com.opensymphony.xwork2.util.location.Location; + +public class XWorkExceptionTest extends XWorkTestCase { + + public void testUnknown() throws Exception { + XWorkException e = new XWorkException("testXXX", this); + assertEquals(Location.UNKNOWN, e.getLocation()); + } + + public void testThrowable() { + XWorkException e = new XWorkException("testThrowable", new IllegalArgumentException("Arg is null")); + assertEquals("com/opensymphony/xwork2/XWorkExceptionTest.java", e.getLocation().getURI()); + String s = e.getLocation().toString(); + assertTrue(s.contains("Method: testThrowable")); + } + + public void testCauseAndTarget() { + XWorkException e = new XWorkException(new IllegalArgumentException("Arg is null"), this); + assertEquals("com/opensymphony/xwork2/XWorkExceptionTest.java", e.getLocation().getURI()); + String s = e.getLocation().toString(); + assertTrue(s.contains("Method: testCauseAndTarget")); + } + + public void testDefaultConstructor() { + XWorkException e = new XWorkException(); + + assertNull(e.getCause()); + assertNull(e.getThrowable()); + assertNull(e.getMessage()); + assertNull(e.getLocation()); + + assertNull(e.toString()); // mo message so it returns null + } + + public void testMessageOnly() { + XWorkException e = new XWorkException("Hello World"); + + assertNull(e.getCause()); + assertEquals("Hello World", e.getMessage()); + assertEquals(Location.UNKNOWN, e.getLocation()); + } + + public void testCauseOnly() { + XWorkException e = new XWorkException(new IllegalArgumentException("Arg is null")); + + assertNotNull(e.getCause()); + assertNotNull(e.getLocation()); + assertEquals("com/opensymphony/xwork2/XWorkExceptionTest.java", e.getLocation().getURI()); + String s = e.getLocation().toString(); + assertTrue(s.contains("Method: testCauseOnly")); + assertTrue(e.toString().contains("Arg is null")); + } + + public void testCauseOnlyNoMessage() { + XWorkException e = new XWorkException(new IllegalArgumentException()); + + assertNotNull(e.getCause()); + assertNotNull(e.getLocation()); + assertEquals("com/opensymphony/xwork2/XWorkExceptionTest.java", e.getLocation().getURI()); + String s = e.getLocation().toString(); + assertTrue(s.contains("Method: testCauseOnly")); + assertTrue(e.toString().contains("Method: testCauseOnly")); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/ConfigurationManagerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/ConfigurationManagerTest.java new file mode 100644 index 000000000..a50711e81 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/ConfigurationManagerTest.java @@ -0,0 +1,181 @@ +/* + * 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; + +//import org.easymock.MockControl; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +import java.util.Properties; + + +/** + * ConfigurationManagerTest + * + * @author Jason Carreira + * Created May 6, 2003 10:59:59 PM + */ +public class ConfigurationManagerTest extends XWorkTestCase { + + Mock configProviderMock; + + + public void testConfigurationReload() { + FileManager.setReloadingConfigs(true); + + // now check that it reloads + configProviderMock.expectAndReturn("needsReload", Boolean.TRUE); + configProviderMock.expect("init", C.isA(Configuration.class)); + configProviderMock.expect("register", C.ANY_ARGS); + configProviderMock.expect("loadPackages", C.ANY_ARGS); + configProviderMock.expect("destroy", C.ANY_ARGS); + configProviderMock.matchAndReturn("toString", "mock"); + configurationManager.getConfiguration(); + configProviderMock.verify(); + + // this will be called in teardown + configProviderMock.expect("destroy"); + } + + public void testNoConfigurationReload() { + FileManager.setReloadingConfigs(false); + + // now check that it doesn't try to reload + configurationManager.getConfiguration(); + configProviderMock.verify(); + + // this will be called in teardown + configProviderMock.expect("destroy"); + } + + public void testDestroyConfiguration() throws Exception { + class State { + public boolean isDestroyed1 =false; + public boolean isDestroyed2 =false; + }; + + final State state = new State(); + ConfigurationManager configurationManager = new ConfigurationManager(); + configurationManager.addConfigurationProvider(new ConfigurationProvider() { + public void destroy() { + throw new RuntimeException("testing testing 123"); + } + public void init(Configuration configuration) throws ConfigurationException { + } + public void loadPackages() throws ConfigurationException { + } + public boolean needsReload() { return false; + } + public void register(ContainerBuilder builder, Properties props) throws ConfigurationException { + } + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + } + }); + configurationManager.addConfigurationProvider(new ConfigurationProvider() { + public void destroy() { + state.isDestroyed1 = true; + } + public void init(Configuration configuration) throws ConfigurationException { + } + public void loadPackages() throws ConfigurationException { + } + public boolean needsReload() { return false; + } + public void register(ContainerBuilder builder, Properties props) throws ConfigurationException { + } + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + } + }); + configurationManager.addConfigurationProvider(new ConfigurationProvider() { + public void destroy() { + throw new RuntimeException("testing testing 123"); + } + public void init(Configuration configuration) throws ConfigurationException { + } + public void loadPackages() throws ConfigurationException { + } + public boolean needsReload() { return false; + } + public void register(ContainerBuilder builder, Properties props) throws ConfigurationException { + } + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + } + }); + configurationManager.addConfigurationProvider(new ConfigurationProvider() { + public void destroy() { + state.isDestroyed2 = true; + } + public void init(Configuration configuration) throws ConfigurationException { + } + public void loadPackages() throws ConfigurationException { + } + public boolean needsReload() { return false; + } + public void register(ContainerBuilder builder, Properties props) throws ConfigurationException { + } + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + } + }); + + assertFalse(state.isDestroyed1); + assertFalse(state.isDestroyed2); + + configurationManager.clearConfigurationProviders(); + + assertTrue(state.isDestroyed1); + assertTrue(state.isDestroyed2); + } + + public void testClearConfigurationProviders() throws Exception { + configProviderMock.expect("destroy"); + configurationManager.clearConfigurationProviders(); + configProviderMock.verify(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + configurationManager.destroyConfiguration(); + + configProviderMock = new Mock(ConfigurationProvider.class); + configProviderMock.matchAndReturn("equals", C.ANY_ARGS, false); + + ConfigurationProvider mockProvider = (ConfigurationProvider) configProviderMock.proxy(); + configurationManager.addConfigurationProvider(new XWorkConfigurationProvider()); + configurationManager.addConfigurationProvider(mockProvider); + + //the first time it always inits + configProviderMock.expect("init", C.isA(Configuration.class)); + configProviderMock.expect("register", C.ANY_ARGS); + configProviderMock.expect("loadPackages", C.ANY_ARGS); + configProviderMock.matchAndReturn("toString", "mock"); + + configurationManager.getConfiguration(); + } + + @Override + protected void tearDown() throws Exception { + configProviderMock.expect("destroy"); + FileManager.setReloadingConfigs(true); + super.tearDown(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java new file mode 100644 index 000000000..22bc1af55 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java @@ -0,0 +1,322 @@ +/* + * 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; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.mock.MockInterceptor; +import com.opensymphony.xwork2.test.StubConfigurationProvider; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +import java.util.HashMap; +import java.util.Map; + + +/** + * ConfigurationTest + *

+ * Created : Jan 27, 2003 1:30:08 AM + * + * @author Jason Carreira + */ +public class ConfigurationTest extends XWorkTestCase { + + public void testAbstract() { + try { + actionProxyFactory.createActionProxy("/abstract", "test", null); + fail(); + } catch (Exception e) { + // this is what we expected + } + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("/nonAbstract", "test", null); + assertTrue(proxy.getActionName().equals("test")); + assertTrue(proxy.getConfig().getClassName().equals(SimpleAction.class.getName())); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testDefaultNamespace() { + HashMap params = new HashMap(); + params.put("blah", "this is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("/does/not/exist", "Foo", extraContext); + proxy.execute(); + assertEquals("this is blah", proxy.getInvocation().getStack().findValue("[1].blah")); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testFileIncludeLoader() { + RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration(); + + // check entityTest package + assertNotNull(configuration.getActionConfig("includeTest", "includeTest")); + + // check inheritance from Default + assertNotNull(configuration.getActionConfig("includeTest", "Foo")); + } + + public void testWildcardName() { + RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration(); + + ActionConfig config = configuration.getActionConfig("", "WildCard/Simple/input"); + + assertNotNull(config); + assertTrue("Wrong class name, "+config.getClassName(), + "com.opensymphony.xwork2.SimpleAction".equals(config.getClassName())); + assertTrue("Wrong method name", "input".equals(config.getMethodName())); + + Map p = config.getParams(); + assertTrue("Wrong parameter, "+p.get("foo"), "Simple".equals(p.get("foo"))); + assertTrue("Wrong parameter, "+p.get("bar"), "input".equals(p.get("bar"))); + } + + public void testWildcardNamespace() { + RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration(); + + ActionConfig config = configuration.getActionConfig("/animals/dog", "commandTest"); + + assertNotNull(config); + assertTrue("Wrong class name, "+config.getClassName(), + "com.opensymphony.xwork2.SimpleAction".equals(config.getClassName())); + + Map p = config.getParams(); + assertTrue("Wrong parameter, "+p.get("0"), "/animals/dog".equals(p.get("0"))); + assertTrue("Wrong parameter, "+p.get("1"), "dog".equals(p.get("1"))); + } + + public void testGlobalResults() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "Foo", null); + assertNotNull(proxy.getConfig().getResults().get("login")); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testInterceptorParamInehritanceOverride() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("/foo/bar", "TestInterceptorParamInehritanceOverride", null); + assertEquals(1, proxy.getConfig().getInterceptors().size()); + + MockInterceptor testInterceptor = (MockInterceptor) ((InterceptorMapping) proxy.getConfig().getInterceptors().get(0)).getInterceptor(); + assertEquals("foo123", testInterceptor.getExpectedFoo()); + proxy.execute(); + assertTrue(testInterceptor.isExecuted()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testInterceptorParamInheritance() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("/foo/bar", "TestInterceptorParamInheritance", null); + assertEquals(1, proxy.getConfig().getInterceptors().size()); + + MockInterceptor testInterceptor = (MockInterceptor) ((InterceptorMapping) proxy.getConfig().getInterceptors().get(0)).getInterceptor(); + assertEquals("expectedFoo", testInterceptor.getExpectedFoo()); + proxy.execute(); + assertTrue(testInterceptor.isExecuted()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testInterceptorParamOverride() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "TestInterceptorParamOverride", null); + assertEquals(1, proxy.getConfig().getInterceptors().size()); + + MockInterceptor testInterceptor = (MockInterceptor) ((InterceptorMapping) proxy.getConfig().getInterceptors().get(0)).getInterceptor(); + assertEquals("foo123", testInterceptor.getExpectedFoo()); + proxy.execute(); + assertTrue(testInterceptor.isExecuted()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testInterceptorParams() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "TestInterceptorParam", null); + assertEquals(1, proxy.getConfig().getInterceptors().size()); + + MockInterceptor testInterceptor = (MockInterceptor) ((InterceptorMapping) proxy.getConfig().getInterceptors().get(0)).getInterceptor(); + assertEquals("expectedFoo", testInterceptor.getExpectedFoo()); + proxy.execute(); + assertTrue(testInterceptor.isExecuted()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testMultipleConfigProviders() { + configurationManager.addConfigurationProvider(new MockConfigurationProvider()); + + try { + configurationManager.reload(); + } catch (ConfigurationException e) { + e.printStackTrace(); + fail(); + } + + RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration(); + + // check that it has configuration from xml + assertNotNull(configuration.getActionConfig("/foo/bar", "Bar")); + + // check that it has configuration from MockConfigurationProvider + assertNotNull(configuration.getActionConfig("", MockConfigurationProvider.FOO_ACTION_NAME)); + } + + public void testMultipleContainerProviders() throws Exception { + System.out.println("-----"); + Mock mockContainerProvider = new Mock(ContainerProvider.class); + mockContainerProvider.expect("init", C.ANY_ARGS); + mockContainerProvider.expect("register", C.ANY_ARGS); + mockContainerProvider.matchAndReturn("equals", C.ANY_ARGS, false); + mockContainerProvider.matchAndReturn("toString", "foo"); + mockContainerProvider.matchAndReturn("destroy", null); + mockContainerProvider.expectAndReturn("needsReload", true); + configurationManager.addContainerProvider((ContainerProvider) mockContainerProvider.proxy()); + + Configuration config = null; + try { + config = configurationManager.getConfiguration(); + } catch (ConfigurationException e) { + e.printStackTrace(); + fail(); + } + + + RuntimeConfiguration configuration = config.getRuntimeConfiguration(); + + // check that it has configuration from xml + assertNotNull(configuration.getActionConfig("/foo/bar", "Bar")); + + System.out.println("-----"); + mockContainerProvider.verify(); + } + + public void testInitForPackageProviders() { + + loadConfigurationProviders(new StubConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, + LocatableProperties props) throws ConfigurationException { + builder.factory(PackageProvider.class, "foo", MyPackageProvider.class); + } + }); + + assertEquals(configuration, MyPackageProvider.getConfiguration()); + } + + public void testInitOnceForConfigurationProviders() { + + loadConfigurationProviders(new StubConfigurationProvider() { + boolean called = false; + @Override + public void init(Configuration config) { + if (called) { + fail("Called twice"); + } + called = true; + } + + @Override + public void loadPackages() { + if (!called) { + fail("Never called"); + } + } + }); + } + + public void testMultipleInheritance() { + try { + ActionProxy proxy; + proxy = actionProxyFactory.createActionProxy("multipleInheritance", "test", null); + assertNotNull(proxy); + proxy = actionProxyFactory.createActionProxy("multipleInheritance", "Foo", null); + assertNotNull(proxy); + proxy = actionProxyFactory.createActionProxy("multipleInheritance", "testMultipleInheritance", null); + assertNotNull(proxy); + assertEquals(5, proxy.getConfig().getInterceptors().size()); + assertEquals(2, proxy.getConfig().getResults().size()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testPackageExtension() { + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("/foo/bar", "Bar", null); + assertEquals(5, proxy.getConfig().getInterceptors().size()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + } + + public static class MyPackageProvider implements PackageProvider { + static Configuration config; + public void loadPackages() throws ConfigurationException {} + public boolean needsReload() { return config != null; } + + public static Configuration getConfiguration() { + return config; + } + public void init(Configuration configuration) + throws ConfigurationException { + config = configuration; + } + + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/entities/ActionConfigTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/entities/ActionConfigTest.java new file mode 100644 index 000000000..d22bd7477 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/entities/ActionConfigTest.java @@ -0,0 +1,44 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.location.LocationImpl; + +/** + * ActionConfigTest + */ +public class ActionConfigTest extends XWorkTestCase { + + public void testToString() { + ActionConfig cfg = new ActionConfig.Builder("", "bob", "foo.Bar") + .methodName("execute") + .location(new LocationImpl(null, "foo/xwork.xml", 10, 12)) + .build(); + + assertTrue("Wrong toString(): "+cfg.toString(), + "{ActionConfig bob (foo.Bar.execute()) - foo/xwork.xml:10:12}".equals(cfg.toString())); + } + + public void testToStringWithNoMethod() { + ActionConfig cfg = new ActionConfig.Builder("", "bob", "foo.Bar") + .location(new LocationImpl(null, "foo/xwork.xml", 10, 12)) + .build(); + + assertTrue("Wrong toString(): "+cfg.toString(), + "{ActionConfig bob (foo.Bar) - foo/xwork.xml:10:12}".equals(cfg.toString())); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/entities/PackageConfigTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/entities/PackageConfigTest.java new file mode 100644 index 000000000..e2f2868c2 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/entities/PackageConfigTest.java @@ -0,0 +1,34 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.XWorkTestCase; + +public class PackageConfigTest extends XWorkTestCase { + + public void testFullDefaultInterceptorRef() { + PackageConfig cfg1 = new PackageConfig.Builder("pkg1") + .defaultInterceptorRef("ref1").build(); + PackageConfig cfg2 = new PackageConfig.Builder("pkg2").defaultInterceptorRef("ref2").build(); + PackageConfig cfg = new PackageConfig.Builder("pkg") + .addParent(cfg1) + .addParent(cfg2) + .build(); + + assertEquals("ref2", cfg.getFullDefaultInterceptorRef()); + } + +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcherTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcherTest.java new file mode 100644 index 000000000..6616413cc --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcherTest.java @@ -0,0 +1,164 @@ +/* + * $Id$ + * + * Copyright 1999-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.XWorkTestCase; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.ResultConfig; + +import java.util.HashMap; +import java.util.Map; + +public class ActionConfigMatcherTest extends XWorkTestCase { + + // ----------------------------------------------------- Instance Variables + private Map configMap; + private ActionConfigMatcher matcher; + + // ----------------------------------------------------- Setup and Teardown + @Override public void setUp() throws Exception { + super.setUp(); + configMap = buildActionConfigMap(); + matcher = new ActionConfigMatcher(configMap); + } + + @Override public void tearDown() throws Exception { + super.tearDown(); + } + + // ------------------------------------------------------- Individual Tests + // ---------------------------------------------------------- match() + public void testNoMatch() { + assertNull("ActionConfig shouldn't be matched", matcher.match("test")); + } + + public void testNoWildcardMatch() { + assertNull("ActionConfig shouldn't be matched", matcher.match("noWildcard")); + } + + public void testShouldMatch() { + ActionConfig matched = matcher.match("foo/class/method"); + + assertNotNull("ActionConfig should be matched", matched); + assertTrue("ActionConfig should have properties, had " + + matched.getParams().size(), matched.getParams().size() == 2); + assertTrue("ActionConfig should have interceptors", + matched.getInterceptors().size() == 1); + assertTrue("ActionConfig should have ex mappings", + matched.getExceptionMappings().size() == 1); + assertTrue("ActionConfig should have external refs", + matched.getExceptionMappings().size() == 1); + assertTrue("ActionConfig should have results", + matched.getResults().size() == 1); + } + + public void testCheckSubstitutionsMatch() { + ActionConfig m = matcher.match("foo/class/method"); + + assertTrue("Class hasn't been replaced", "foo.bar.classAction".equals(m.getClassName())); + assertTrue("Method hasn't been replaced", "domethod".equals(m.getMethodName())); + assertTrue("Package isn't correct", "package-class".equals(m.getPackageName())); + + assertTrue("First param isn't correct", "class".equals(m.getParams().get("first"))); + assertTrue("Second param isn't correct", "method".equals(m.getParams().get("second"))); + + ExceptionMappingConfig ex = m.getExceptionMappings().get(0); + assertTrue("Wrong name, was "+ex.getName(), "fooclass".equals(ex.getName())); + assertTrue("Wrong result", "successclass".equals(ex.getResult())); + assertTrue("Wrong exception", + "java.lang.methodException".equals(ex.getExceptionClassName())); + assertTrue("First param isn't correct", "class".equals(ex.getParams().get("first"))); + assertTrue("Second param isn't correct", "method".equals(ex.getParams().get("second"))); + + ResultConfig result = m.getResults().get("successclass"); + assertTrue("Wrong name, was "+result.getName(), "successclass".equals(result.getName())); + assertTrue("Wrong classname", "foo.method".equals(result.getClassName())); + assertTrue("First param isn't correct", "class".equals(result.getParams().get("first"))); + assertTrue("Second param isn't correct", "method".equals(result.getParams().get("second"))); + + } + + public void testCheckMultipleSubstitutions() { + ActionConfig m = matcher.match("bar/class/method/more"); + + assertTrue("Method hasn't been replaced correctly: " + m.getMethodName(), + "doclass_class".equals(m.getMethodName())); + } + + public void testLooseMatch() { + configMap.put("*!*", configMap.get("bar/*/**")); + ActionConfigMatcher matcher = new ActionConfigMatcher(configMap, true); + + // exact match + ActionConfig m = matcher.match("foo/class/method"); + assertNotNull("ActionConfig should be matched", m); + assertTrue("Class hasn't been replaced "+m.getClassName(), "foo.bar.classAction".equals(m.getClassName())); + assertTrue("Method hasn't been replaced", "domethod".equals(m.getMethodName())); + + // Missing last wildcard + m = matcher.match("foo/class"); + assertNotNull("ActionConfig should be matched", m); + assertTrue("Class hasn't been replaced", "foo.bar.classAction".equals(m.getClassName())); + assertTrue("Method hasn't been replaced, "+m.getMethodName(), "do".equals(m.getMethodName())); + + // Simple mapping + m = matcher.match("class!method"); + assertNotNull("ActionConfig should be matched", m); + assertTrue("Class hasn't been replaced, "+m.getPackageName(), "package-class".equals(m.getPackageName())); + assertTrue("Method hasn't been replaced", "method".equals(m.getParams().get("first"))); + + // Simple mapping + m = matcher.match("class"); + assertNotNull("ActionConfig should be matched", m); + assertTrue("Class hasn't been replaced", "package-class".equals(m.getPackageName())); + assertTrue("Method hasn't been replaced", "".equals(m.getParams().get("first"))); + + } + + private Map buildActionConfigMap() { + Map map = new HashMap(); + + HashMap params = new HashMap(); + params.put("first", "{1}"); + params.put("second", "{2}"); + + ActionConfig config = new ActionConfig.Builder("package-{1}", "foo/*/*", "foo.bar.{1}Action") + .methodName("do{2}") + .addParams(params) + .addExceptionMapping(new ExceptionMappingConfig.Builder("foo{1}", "java.lang.{2}Exception", "success{1}") + .addParams(new HashMap(params)) + .build()) + .addInterceptor(new InterceptorMapping(null, null)) + .addResultConfig(new ResultConfig.Builder("success{1}", "foo.{2}").addParams(params).build()) + .build(); + map.put("foo/*/*", config); + + config = new ActionConfig.Builder("package-{1}", "bar/*/**", "bar") + .methodName("do{1}_{1}") + .addParam("first", "{2}") + .build(); + + map.put("bar/*/**", config); + + map.put("noWildcard", new ActionConfig.Builder("", "", "").build()); + + return map; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/impl/NamespaceMatcherTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/impl/NamespaceMatcherTest.java new file mode 100644 index 000000000..1616704f6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/impl/NamespaceMatcherTest.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.config.impl; + +import com.opensymphony.xwork2.util.WildcardHelper; +import junit.framework.TestCase; + +import java.util.HashSet; +import java.util.Set; + +public class NamespaceMatcherTest extends TestCase { + + public void testMatch() { + Set names = new HashSet(); + names.add("/bar"); + names.add("/foo/*/bar"); + names.add("/foo/*"); + names.add("/foo/*/jim/*"); + NamespaceMatcher matcher = new NamespaceMatcher(new WildcardHelper(), names); + assertEquals(3, matcher.compiledPatterns.size()); + + assertNull(matcher.match("/asd")); + assertEquals("/foo/*", matcher.match("/foo/23").getPattern()); + assertEquals("/foo/*/bar", matcher.match("/foo/23/bar").getPattern()); + assertEquals("/foo/*/jim/*", matcher.match("/foo/23/jim/42").getPattern()); + assertNull(matcher.match("/foo/23/asd")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationTestBase.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationTestBase.java new file mode 100644 index 000000000..175836235 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationTestBase.java @@ -0,0 +1,43 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.impl.MockConfiguration; + + +/** + * ConfigurationTestBase + * + * @author Jason Carreira + * Created Jun 9, 2003 7:42:12 AM + */ +public abstract class ConfigurationTestBase extends XWorkTestCase { + + protected ConfigurationProvider buildConfigurationProvider(final String filename) { + configuration = new MockConfiguration(); + ((MockConfiguration)configuration).selfRegister(); + container = configuration.getContainer(); + + XmlConfigurationProvider prov = new XmlConfigurationProvider(filename, true); + prov.setObjectFactory(container.getInstance(ObjectFactory.class)); + prov.init(configuration); + prov.loadPackages(); + return prov; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/InterceptorBuilderTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/InterceptorBuilderTest.java new file mode 100644 index 000000000..914d9630f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/InterceptorBuilderTest.java @@ -0,0 +1,274 @@ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.InterceptorStackConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.interceptor.Interceptor; + +import java.util.LinkedHashMap; +import java.util.List; + +/** + * InterceptorBuilderTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class InterceptorBuilderTest extends XWorkTestCase { + + ObjectFactory objectFactory; + + @Override + public void setUp() throws Exception { + super.setUp(); + objectFactory = container.getInstance(ObjectFactory.class); + } + + /** + * Try to test this + * + * interceptor1_value1 + * interceptor1_value2 + * interceptor2_value1 + * interceptor2_value2 + * + * + * @throws Exception + */ + public void testBuildInterceptor_1() throws Exception { + InterceptorStackConfig interceptorStackConfig1 = new InterceptorStackConfig.Builder("interceptorStack1").build(); + + InterceptorConfig interceptorConfig1 = new InterceptorConfig.Builder("interceptor1", "com.opensymphony.xwork2.config.providers.InterceptorBuilderTest$MockInterceptor1").build(); + + InterceptorConfig interceptorConfig2 = new InterceptorConfig.Builder("interceptor2", "com.opensymphony.xwork2.config.providers.InterceptorBuilderTest$MockInterceptor2").build(); + + + PackageConfig packageConfig = new PackageConfig.Builder("package1").namespace("/namespace").addInterceptorConfig(interceptorConfig1).addInterceptorConfig(interceptorConfig2).addInterceptorStackConfig(interceptorStackConfig1).build(); + + List + interceptorMappings = + InterceptorBuilder.constructInterceptorReference(packageConfig, "interceptorStack1", + new LinkedHashMap() { + private static final long serialVersionUID = -1358620486812957895L; + + { + put("interceptor1.param1", "interceptor1_value1"); + put("interceptor1.param2", "interceptor1_value2"); + put("interceptor2.param1", "interceptor2_value1"); + put("interceptor2.param2", "interceptor2_value2"); + } + },null, objectFactory); + + assertEquals(interceptorMappings.size(), 2); + + assertEquals(((InterceptorMapping) interceptorMappings.get(0)).getName(), "interceptor1"); + assertNotNull(((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()); + assertEquals(((InterceptorMapping) interceptorMappings.get(0)).getInterceptor().getClass(), MockInterceptor1.class); + assertEquals(((MockInterceptor1) ((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()).getParam1(), "interceptor1_value1"); + assertEquals(((MockInterceptor1) ((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()).getParam2(), "interceptor1_value2"); + + assertEquals(((InterceptorMapping) interceptorMappings.get(1)).getName(), "interceptor2"); + assertNotNull(((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()); + assertEquals(((InterceptorMapping) interceptorMappings.get(1)).getInterceptor().getClass(), MockInterceptor2.class); + assertEquals(((MockInterceptor2) ((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()).getParam1(), "interceptor2_value1"); + assertEquals(((MockInterceptor2) ((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()).getParam2(), "interceptor2_value2"); + } + + /** + * Try to test this + * + * interceptor1_value1 + * interceptor1_value2 + * interceptor2_value1 + * interceptor2_value2 + * + * + * @throws Exception + */ + public void testBuildInterceptor_2() throws Exception { + InterceptorStackConfig interceptorStackConfig1 = new InterceptorStackConfig.Builder("interceptorStack1").build(); + + InterceptorStackConfig interceptorStackConfig2 = new InterceptorStackConfig.Builder("interceptorStack2").build(); + + InterceptorStackConfig interceptorStackConfig3 = new InterceptorStackConfig.Builder("interceptorStack3").build(); + + InterceptorConfig interceptorConfig1 = new InterceptorConfig.Builder("interceptor1", "com.opensymphony.xwork2.config.providers.InterceptorBuilderTest$MockInterceptor1").build(); + + InterceptorConfig interceptorConfig2 = new InterceptorConfig.Builder("interceptor2", "com.opensymphony.xwork2.config.providers.InterceptorBuilderTest$MockInterceptor2").build(); + + + PackageConfig packageConfig = new PackageConfig.Builder("package1").namespace("/namspace"). + addInterceptorConfig(interceptorConfig1). + addInterceptorConfig(interceptorConfig2). + addInterceptorStackConfig(interceptorStackConfig1). + addInterceptorStackConfig(interceptorStackConfig2). + addInterceptorStackConfig(interceptorStackConfig3).build(); + + List interceptorMappings = InterceptorBuilder.constructInterceptorReference(packageConfig, "interceptorStack1", + new LinkedHashMap() { + private static final long serialVersionUID = -5819935102242042570L; + + { + put("interceptorStack2.interceptor1.param1", "interceptor1_value1"); + put("interceptorStack2.interceptor1.param2", "interceptor1_value2"); + put("interceptorStack3.interceptor2.param1", "interceptor2_value1"); + put("interceptorStack3.interceptor2.param2", "interceptor2_value2"); + } + }, null, objectFactory); + + assertEquals(interceptorMappings.size(), 2); + + assertEquals(((InterceptorMapping) interceptorMappings.get(0)).getName(), "interceptor1"); + assertNotNull(((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()); + assertEquals(((InterceptorMapping) interceptorMappings.get(0)).getInterceptor().getClass(), MockInterceptor1.class); + assertEquals(((MockInterceptor1) ((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()).getParam1(), "interceptor1_value1"); + assertEquals(((MockInterceptor1) ((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()).getParam2(), "interceptor1_value2"); + + assertEquals(((InterceptorMapping) interceptorMappings.get(1)).getName(), "interceptor2"); + assertNotNull(((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()); + assertEquals(((InterceptorMapping) interceptorMappings.get(1)).getInterceptor().getClass(), MockInterceptor2.class); + assertEquals(((MockInterceptor2) ((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()).getParam1(), "interceptor2_value1"); + assertEquals(((MockInterceptor2) ((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()).getParam2(), "interceptor2_value2"); + } + + /** + * Try to test this + * + * interceptor1_value1 + * interceptor1_value2 + * interceptor2_value1 + * interceptor2_value2 + * + * + * @throws Exception + */ + public void testBuildInterceptor_3() throws Exception { + InterceptorConfig interceptorConfig1 = new InterceptorConfig.Builder("interceptor1", "com.opensymphony.xwork2.config.providers.InterceptorBuilderTest$MockInterceptor1").build(); + + InterceptorConfig interceptorConfig2 = new InterceptorConfig.Builder("interceptor2", "com.opensymphony.xwork2.config.providers.InterceptorBuilderTest$MockInterceptor2").build(); + + + InterceptorStackConfig interceptorStackConfig1 = new InterceptorStackConfig.Builder("interceptorStack1").build(); + + + InterceptorStackConfig interceptorStackConfig2 = new InterceptorStackConfig.Builder("interceptorStack2").build(); + + + InterceptorStackConfig interceptorStackConfig3 = new InterceptorStackConfig.Builder("interceptorStack3").build(); + + + InterceptorStackConfig interceptorStackConfig4 = new InterceptorStackConfig.Builder("interceptorStack4").build(); + + + InterceptorStackConfig interceptorStackConfig5 = new InterceptorStackConfig.Builder("interceptorStack5").build(); + + + + PackageConfig packageConfig = new PackageConfig.Builder("package1"). + addInterceptorConfig(interceptorConfig1). + addInterceptorConfig(interceptorConfig2). + addInterceptorStackConfig(interceptorStackConfig1). + addInterceptorStackConfig(interceptorStackConfig2). + addInterceptorStackConfig(interceptorStackConfig3). + addInterceptorStackConfig(interceptorStackConfig4). + addInterceptorStackConfig(interceptorStackConfig5).build(); + + + List interceptorMappings = InterceptorBuilder.constructInterceptorReference( + packageConfig, "interceptorStack1", + new LinkedHashMap() { + private static final long serialVersionUID = 4675809753780875525L; + + { + put("interceptorStack2.interceptorStack3.interceptorStack4.interceptor1.param1", "interceptor1_value1"); + put("interceptorStack2.interceptorStack3.interceptorStack4.interceptor1.param2", "interceptor1_value2"); + put("interceptorStack5.interceptor2.param1", "interceptor2_value1"); + put("interceptorStack5.interceptor2.param2", "interceptor2_value2"); + } + }, null, objectFactory); + + assertEquals(interceptorMappings.size(), 2); + + assertEquals(((InterceptorMapping) interceptorMappings.get(0)).getName(), "interceptor1"); + assertNotNull(((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()); + assertEquals(((InterceptorMapping) interceptorMappings.get(0)).getInterceptor().getClass(), MockInterceptor1.class); + assertEquals(((MockInterceptor1) ((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()).getParam1(), "interceptor1_value1"); + assertEquals(((MockInterceptor1) ((InterceptorMapping) interceptorMappings.get(0)).getInterceptor()).getParam2(), "interceptor1_value2"); + + assertEquals(((InterceptorMapping) interceptorMappings.get(1)).getName(), "interceptor2"); + assertNotNull(((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()); + assertEquals(((InterceptorMapping) interceptorMappings.get(1)).getInterceptor().getClass(), MockInterceptor2.class); + assertEquals(((MockInterceptor2) ((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()).getParam1(), "interceptor2_value1"); + assertEquals(((MockInterceptor2) ((InterceptorMapping) interceptorMappings.get(1)).getInterceptor()).getParam2(), "interceptor2_value2"); + } + + + public static class MockInterceptor1 implements Interceptor { + private static final long serialVersionUID = 2939902550126175874L; + private String param1; + private String param2; + + public void setParam1(String param1) { + this.param1 = param1; + } + + public String getParam1() { + return this.param1; + } + + public void setParam2(String param2) { + this.param2 = param2; + } + + public String getParam2() { + return this.param2; + } + + public void destroy() { + } + + public void init() { + } + + public String intercept(ActionInvocation invocation) throws Exception { + return invocation.invoke(); + } + } + + public static class MockInterceptor2 implements Interceptor { + private static final long serialVersionUID = 267427973306989618L; + private String param1; + private String param2; + + public void setParam1(String param1) { + this.param1 = param1; + } + + public String getParam1() { + return this.param1; + } + + public void setParam2(String param2) { + this.param2 = param2; + } + + public String getParam2() { + return this.param2; + } + + public void destroy() { + } + + public void init() { + } + + public String intercept(ActionInvocation invocation) throws Exception { + return invocation.invoke(); + } + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/InterceptorForTestPurpose.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/InterceptorForTestPurpose.java new file mode 100644 index 000000000..89112684a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/InterceptorForTestPurpose.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.providers; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.Interceptor; + +/** + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class InterceptorForTestPurpose implements Interceptor { + + private String paramOne; + private String paramTwo; + + public String getParamOne() { return paramOne; } + public void setParamOne(String paramOne) { this.paramOne = paramOne; } + + public String getParamTwo() { return paramTwo; } + public void setParamTwo(String paramTwo) { this.paramTwo = paramTwo; } + + public void destroy() { + } + + public void init() { + } + + public String intercept(ActionInvocation invocation) throws Exception { + return invocation.invoke(); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/MockConfigurationProvider.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/MockConfigurationProvider.java new file mode 100644 index 000000000..f1d7d648c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/MockConfigurationProvider.java @@ -0,0 +1,193 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.*; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor; +import com.opensymphony.xwork2.interceptor.ParametersInterceptor; +import com.opensymphony.xwork2.interceptor.StaticParametersInterceptor; +import com.opensymphony.xwork2.mock.MockResult; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import com.opensymphony.xwork2.validator.ValidationInterceptor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * MockConfigurationProvider provides a simple configuration class without the need for xml files, etc. for simple testing. + * + * @author $author$ + * @version $Revision$ + */ +public class MockConfigurationProvider implements ConfigurationProvider { + + public static final String FOO_ACTION_NAME = "foo"; + public static final String MODEL_DRIVEN_PARAM_TEST = "modelParamTest"; + public static final String MODEL_DRIVEN_PARAM_FILTER_TEST = "modelParamFilterTest"; + public static final String PARAM_INTERCEPTOR_ACTION_NAME = "parametersInterceptorTest"; + public static final String VALIDATION_ACTION_NAME = "validationInterceptorTest"; + public static final String VALIDATION_ALIAS_NAME = "validationAlias"; + public static final String VALIDATION_SUBPROPERTY_NAME = "subproperty"; + private Configuration configuration; + private Map params; + private ObjectFactory objectFactory; + + public MockConfigurationProvider() {} + public MockConfigurationProvider(Map params) { + this.params = params; + } + + /** + * Allows the configuration to clean up any resources used + */ + public void destroy() { + } + + public void init(Configuration config) { + this.configuration = config; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + public void loadPackages() { + + PackageConfig.Builder defaultPackageContext = new PackageConfig.Builder("defaultPackage"); + HashMap params = new HashMap(); + params.put("bar", "5"); + + HashMap results = new HashMap(); + HashMap successParams = new HashMap(); + successParams.put("actionName", "bar"); + results.put("success", new ResultConfig.Builder("success", ActionChainResult.class.getName()).addParams(successParams).build()); + + ActionConfig fooActionConfig = new ActionConfig.Builder("defaultPackage", FOO_ACTION_NAME, SimpleAction.class.getName()) + .addResultConfig(new ResultConfig.Builder(Action.ERROR, MockResult.class.getName()).build()) + .build(); + defaultPackageContext.addActionConfig(FOO_ACTION_NAME, fooActionConfig); + + results = new HashMap(); + successParams = new HashMap(); + successParams.put("actionName", "bar"); + results.put("success", new ResultConfig.Builder("success", ActionChainResult.class.getName()).addParams(successParams).build()); + + List interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("params", new ParametersInterceptor())); + + ActionConfig paramInterceptorActionConfig = new ActionConfig.Builder("defaultPackage", PARAM_INTERCEPTOR_ACTION_NAME, SimpleAction.class.getName()) + .addResultConfig(new ResultConfig.Builder(Action.ERROR, MockResult.class.getName()).build()) + .addInterceptors(interceptors) + .build(); + defaultPackageContext.addActionConfig(PARAM_INTERCEPTOR_ACTION_NAME, paramInterceptorActionConfig); + + interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("model", + objectFactory.buildInterceptor(new InterceptorConfig.Builder("model", ModelDrivenInterceptor.class.getName()).build(), new HashMap()))); + interceptors.add(new InterceptorMapping("params", + objectFactory.buildInterceptor(new InterceptorConfig.Builder("model", ParametersInterceptor.class.getName()).build(), new HashMap()))); + + ActionConfig modelParamActionConfig = new ActionConfig.Builder("defaultPackage", MODEL_DRIVEN_PARAM_TEST, ModelDrivenAction.class.getName()) + .addInterceptors(interceptors) + .addResultConfig(new ResultConfig.Builder(Action.SUCCESS, MockResult.class.getName()).build()) + .build(); + defaultPackageContext.addActionConfig(MODEL_DRIVEN_PARAM_TEST, modelParamActionConfig); + + //List paramFilterInterceptor=new ArrayList(); + //paramFilterInterceptor.add(new ParameterFilterInterC) + //ActionConfig modelParamFilterActionConfig = new ActionConfig(null, ModelDrivenAction.class, null, null, interceptors); + + + results = new HashMap(); + successParams = new HashMap(); + successParams.put("actionName", "bar"); + results.put("success", new ResultConfig.Builder("success", ActionChainResult.class.getName()).addParams(successParams).build()); + results.put(Action.ERROR, new ResultConfig.Builder(Action.ERROR, MockResult.class.getName()).build()); + + interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("staticParams", + objectFactory.buildInterceptor(new InterceptorConfig.Builder("model", StaticParametersInterceptor.class.getName()).build(), new HashMap()))); + interceptors.add(new InterceptorMapping("model", + objectFactory.buildInterceptor(new InterceptorConfig.Builder("model", ModelDrivenInterceptor.class.getName()).build(), new HashMap()))); + interceptors.add(new InterceptorMapping("params", + objectFactory.buildInterceptor(new InterceptorConfig.Builder("model", ParametersInterceptor.class.getName()).build(), new HashMap()))); + interceptors.add(new InterceptorMapping("validation", + objectFactory.buildInterceptor(new InterceptorConfig.Builder("model", ValidationInterceptor.class.getName()).build(), new HashMap()))); + + //Explicitly set an out-of-range date for DateRangeValidatorTest + params = new HashMap(); + params.put("date", new java.util.Date(2002 - 1900, 11, 20)); + + //Explicitly set an out-of-range double for DoubleRangeValidatorTest + params.put("percentage", new Double(100.0123)); + + ActionConfig validationActionConfig = new ActionConfig.Builder("defaultPackage", VALIDATION_ACTION_NAME, SimpleAction.class.getName()) + .addInterceptors(interceptors) + .addParams(params) + .addResultConfigs(results) + .build(); + defaultPackageContext.addActionConfig(VALIDATION_ACTION_NAME, validationActionConfig); + defaultPackageContext.addActionConfig(VALIDATION_ALIAS_NAME, + new ActionConfig.Builder(validationActionConfig).name(VALIDATION_ALIAS_NAME).build()); + defaultPackageContext.addActionConfig(VALIDATION_SUBPROPERTY_NAME, + new ActionConfig.Builder(validationActionConfig).name(VALIDATION_SUBPROPERTY_NAME).build()); + + + params = new HashMap(); + params.put("percentage", new Double(1.234567)); + ActionConfig percentageActionConfig = new ActionConfig.Builder("defaultPackage", "percentage", SimpleAction.class.getName()) + .addParams(params) + .addResultConfigs(results) + .addInterceptors(interceptors) + .build(); + defaultPackageContext.addActionConfig(percentageActionConfig.getName(), percentageActionConfig); + + // We need this actionconfig to be the final destination for action chaining + ActionConfig barActionConfig = new ActionConfig.Builder("defaultPackage", "bar", SimpleAction.class.getName()) + .addResultConfig(new ResultConfig.Builder(Action.ERROR, MockResult.class.getName()).build()) + .build(); + defaultPackageContext.addActionConfig(barActionConfig.getName(), barActionConfig); + + configuration.addPackageConfig("defaultPackage", defaultPackageContext.build()); + } + + /** + * Tells whether the ConfigurationProvider should reload its configuration + * + * @return false + */ + public boolean needsReload() { + return false; + } + + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + if (params != null) { + for (String key : params.keySet()) { + props.setProperty(key, params.get(key)); + } + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/NoNoArgsConstructorAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/NoNoArgsConstructorAction.java new file mode 100644 index 000000000..8f1435d9d --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/NoNoArgsConstructorAction.java @@ -0,0 +1,24 @@ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.Action; + +/** + * Action with no public constructor taking no args. + *

+ * Used for unit test of {@link com.opensymphony.xwork2.config.providers.XmlConfigurationProvider}. + * + * @author Claus Ibsen + */ +public class NoNoArgsConstructorAction implements Action { + + private int foo; + + public NoNoArgsConstructorAction(int foo) { + this.foo = foo; + } + + public String execute() throws Exception { + return SUCCESS; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/PrivateConstructorAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/PrivateConstructorAction.java new file mode 100644 index 000000000..640a17140 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/PrivateConstructorAction.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.providers; + +import com.opensymphony.xwork2.Action; + +/** + * Action with nu public constructor. + *

+ * Used for unit test of {@link XmlConfigurationProvider}. + * + * @author Claus Ibsen + */ +public class PrivateConstructorAction implements Action { + + private int foo; + + private PrivateConstructorAction() { + // should be private, no constructor + } + + public String execute() throws Exception { + return SUCCESS; + } + + public void setFoo(int foo) { + this.foo = foo; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/SomeUnknownHandler.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/SomeUnknownHandler.java new file mode 100644 index 000000000..494a16c0f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/SomeUnknownHandler.java @@ -0,0 +1,48 @@ +/* + * 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.ActionContext; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.UnknownHandler; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.config.entities.ActionConfig; + +public class SomeUnknownHandler implements UnknownHandler{ + private ActionConfig actionConfig; + private String actionMethodResult; + + public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException { + return actionConfig; + } + + public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException { + return actionMethodResult; + } + + public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, + String resultCode) throws XWorkException { + return null; + } + + public void setActionConfig(ActionConfig actionConfig) { + this.actionConfig = actionConfig; + } + + public void setActionMethodResult(String actionMethodResult) { + this.actionMethodResult = actionMethodResult; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderActionsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderActionsTest.java new file mode 100644 index 000000000..d27e7b8d6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderActionsTest.java @@ -0,0 +1,217 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.*; +import com.opensymphony.xwork2.interceptor.TimerInterceptor; +import com.opensymphony.xwork2.mock.MockInterceptor; +import com.opensymphony.xwork2.mock.MockResult; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * @author Mike + * @author Rainer Hermanns + */ +public class XmlConfigurationProviderActionsTest extends ConfigurationTestBase { + + private List interceptors; + private List exceptionMappings; + private Map params; + private Map results; + private ObjectFactory objectFactory; + + + public void testActions() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-actions.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // setup expectations + // bar action is very simple, just two params + params.put("foo", "17"); + params.put("bar", "23"); + params.put("testXW412", "foo.jspa?fooID=${fooID}&something=bar"); + params.put("testXW412Again", "something"); + + + ActionConfig barAction = new ActionConfig.Builder("", "Bar", SimpleAction.class.getName()) + .addParams(params).build(); + + // foo action is a little more complex, two params, a result and an interceptor stack + results = new HashMap(); + params = new HashMap(); + params.put("foo", "18"); + params.put("bar", "24"); + results.put("success", new ResultConfig.Builder("success", MockResult.class.getName()).build()); + + InterceptorConfig timerInterceptorConfig = new InterceptorConfig.Builder("timer", TimerInterceptor.class.getName()).build(); + interceptors.add(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptorConfig, new HashMap()))); + + ActionConfig fooAction = new ActionConfig.Builder("", "Foo", SimpleAction.class.getName()) + .addParams(params) + .addResultConfigs(results) + .addInterceptors(interceptors) + .build(); + + // wildcard action is simple wildcard example + results = new HashMap(); + results.put("*", new ResultConfig.Builder("*", MockResult.class.getName()).build()); + + ActionConfig wildcardAction = new ActionConfig.Builder("", "WildCard", SimpleAction.class.getName()) + .addResultConfigs(results) + .addInterceptors(interceptors) + .build(); + + // fooBar action is a little more complex, two params, a result and an interceptor stack + params = new HashMap(); + params.put("foo", "18"); + params.put("bar", "24"); + results = new HashMap(); + results.put("success", new ResultConfig.Builder("success", MockResult.class.getName()).build()); + + ExceptionMappingConfig exceptionConfig = new ExceptionMappingConfig.Builder("runtime", "java.lang.RuntimeException", "exception") + .build(); + exceptionMappings.add(exceptionConfig); + + ActionConfig fooBarAction = new ActionConfig.Builder("", "FooBar", SimpleAction.class.getName()) + .addParams(params) + .addResultConfigs(results) + .addInterceptors(interceptors) + .addExceptionMappings(exceptionMappings) + .build(); + + // TestInterceptorParam action tests that an interceptor worked + HashMap interceptorParams = new HashMap(); + interceptorParams.put("expectedFoo", "expectedFooValue"); + interceptorParams.put("foo", MockInterceptor.DEFAULT_FOO_VALUE); + + InterceptorConfig mockInterceptorConfig = new InterceptorConfig.Builder("test", MockInterceptor.class.getName()).build(); + interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("test", objectFactory.buildInterceptor(mockInterceptorConfig, interceptorParams))); + + ActionConfig intAction = new ActionConfig.Builder("", "TestInterceptorParam", SimpleAction.class.getName()) + .addInterceptors(interceptors) + .build(); + + // TestInterceptorParamOverride action tests that an interceptor with a param override worked + interceptorParams = new HashMap(); + interceptorParams.put("expectedFoo", "expectedFooValue"); + interceptorParams.put("foo", "foo123"); + interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("test", objectFactory.buildInterceptor(mockInterceptorConfig, interceptorParams))); + + ActionConfig intOverAction = new ActionConfig.Builder("", "TestInterceptorParamOverride", SimpleAction.class.getName()) + .addInterceptors(interceptors) + .build(); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertEquals(7, actionConfigs.size()); + assertEquals(barAction, actionConfigs.get("Bar")); + assertEquals(fooAction, actionConfigs.get("Foo")); + assertEquals(wildcardAction, actionConfigs.get("WildCard")); + assertEquals(fooBarAction, actionConfigs.get("FooBar")); + assertEquals(intAction, actionConfigs.get("TestInterceptorParam")); + assertEquals(intOverAction, actionConfigs.get("TestInterceptorParamOverride")); + } + + public void testInvalidActions() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-action-invalid.xml"; + + try { + ConfigurationProvider provider = buildConfigurationProvider(filename); + fail("Should have thrown an exception"); + } catch (ConfigurationException ex) { + // it worked correctly + } + } + + public void testPackageDefaultClassRef() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-actions-packagedefaultclassref.xml"; + final String testDefaultClassName = "com.opensymphony.xwork2.UserSpecifiedDefaultAction"; + + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // setup expectations + params.put("foo", "17"); + params.put("bar", "23"); + + ActionConfig barWithPackageDefaultClassRefConfig = + new ActionConfig.Builder("", "Bar", "").addParams(params).build(); + + // execute the configuration + provider.init(configuration); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertEquals(1, actionConfigs.size()); + assertEquals(barWithPackageDefaultClassRefConfig, actionConfigs.get("Bar")); + + + } + + public void testDefaultActionClass() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-actions.xml"; + final String testDefaultClassName = "com.opensymphony.xwork2.ActionSupport"; + + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // setup expectations + params.put("foo", "17"); + params.put("bar", "23"); + + ActionConfig barWithoutClassNameConfig = + new ActionConfig.Builder("", "BarWithoutClassName", "").addParams(params).build(); + + // execute the configuration + provider.init(configuration); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertEquals(7, actionConfigs.size()); + assertEquals(barWithoutClassNameConfig, actionConfigs.get("BarWithoutClassName")); + + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + params = new HashMap(); + results = new HashMap(); + interceptors = new ArrayList(); + exceptionMappings = new ArrayList(); + this.objectFactory = container.getInstance(ObjectFactory.class); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderExceptionMappingsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderExceptionMappingsTest.java new file mode 100644 index 000000000..6f2af5cc5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderExceptionMappingsTest.java @@ -0,0 +1,65 @@ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.ActionChainResult; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.mock.MockResult; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * User: Matthew E. Porter (matthew dot porter at metissian dot com) + * Date: Aug 15, 2005 + * Time: 2:05:36 PM + */ +public class XmlConfigurationProviderExceptionMappingsTest extends ConfigurationTestBase { + + public void testActions() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-exception-mappings.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + List exceptionMappings = new ArrayList(); + HashMap parameters = new HashMap(); + HashMap results = new HashMap(); + + exceptionMappings.add( + new ExceptionMappingConfig.Builder("spooky-result", "com.opensymphony.xwork2.SpookyException", "spooky-result") + .build()); + results.put("spooky-result", new ResultConfig.Builder("spooky-result", MockResult.class.getName()).build()); + + Map resultParams = new HashMap(); + resultParams.put("actionName", "bar.vm"); + results.put("specificLocationResult", + new ResultConfig.Builder("specificLocationResult", ActionChainResult.class.getName()) + .addParams(resultParams) + .build()); + + ActionConfig expectedAction = new ActionConfig.Builder("default", "Bar", SimpleAction.class.getName()) + .addParams(parameters) + .addResultConfigs(results) + .addExceptionMappings(exceptionMappings) + .build(); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertEquals(1, actionConfigs.size()); + + ActionConfig action = (ActionConfig) actionConfigs.get("Bar"); + assertEquals(expectedAction, action); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderGlobalResultInheritenceTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderGlobalResultInheritenceTest.java new file mode 100644 index 000000000..41f87382a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderGlobalResultInheritenceTest.java @@ -0,0 +1,53 @@ +package com.opensymphony.xwork2.config.providers; + + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; + +/** + * XmlConfigurationProviderGlobalResultInheritenceTest + * + * @author Rainer Hermanns + * @author tm_jee + * @version $Id$ + */ +public class XmlConfigurationProviderGlobalResultInheritenceTest extends ConfigurationTestBase { + + public void testGlobalResultInheritenceTest() throws Exception { + ConfigurationProvider provider = buildConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-global-result-inheritence.xml"); + + ConfigurationManager configurationManager = new ConfigurationManager(); + configurationManager.addConfigurationProvider(new XWorkConfigurationProvider()); + configurationManager.addConfigurationProvider(provider); + Configuration configuration = configurationManager.getConfiguration(); + + ActionConfig parentActionConfig = configuration.getRuntimeConfiguration().getActionConfig("/base", "parentAction"); + ActionConfig anotherActionConfig = configuration.getRuntimeConfiguration().getActionConfig("/base", "anotherAction"); + ActionConfig childActionConfig = configuration.getRuntimeConfiguration().getActionConfig("/base", "childAction"); + + ResultConfig parentResultConfig1 = (ResultConfig) parentActionConfig.getResults().get("mockResult1"); + ResultConfig parentResultConfig2 = (ResultConfig) parentActionConfig.getResults().get("mockResult2"); + ResultConfig anotherResultConfig1 = (ResultConfig) anotherActionConfig.getResults().get("mockResult1"); + ResultConfig anotherResultConfig2 = (ResultConfig) anotherActionConfig.getResults().get("mockResult2"); + ResultConfig childResultConfig1 = (ResultConfig) childActionConfig.getResults().get("mockResult1"); + ResultConfig childResultConfig2 = (ResultConfig) childActionConfig.getResults().get("mockResult2"); + + System.out.println(parentResultConfig1.getParams().get("identity")); + System.out.println(parentResultConfig2.getParams().get("identity")); + System.out.println(anotherResultConfig1.getParams().get("identity")); + System.out.println(anotherResultConfig2.getParams().get("identity")); + System.out.println(childResultConfig1.getParams().get("identity")); + System.out.println(childResultConfig2.getParams().get("identity")); + + assertFalse(parentResultConfig1 == anotherResultConfig1); + assertFalse(parentResultConfig2 == anotherResultConfig2); + + assertFalse(parentResultConfig1 == childResultConfig1); + assertTrue(parentResultConfig2 == childResultConfig2); + } +} + + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorParamOverridingTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorParamOverridingTest.java new file mode 100644 index 000000000..ed263d4c0 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorParamOverridingTest.java @@ -0,0 +1,95 @@ +/* + * 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.XWorkTestCase; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.RuntimeConfiguration; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.impl.DefaultConfiguration; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author tm_jee + * @version $Date$ $Id$ + */ +public class XmlConfigurationProviderInterceptorParamOverridingTest extends XWorkTestCase { + + public void testInterceptorParamOveriding() throws Exception { + DefaultConfiguration conf = new DefaultConfiguration(); + final XmlConfigurationProvider p = new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-interceptor-param-overriding.xml"); + conf.reload(new ArrayList() { + { + add(new XWorkConfigurationProvider()); + add(p); + } + }); + + RuntimeConfiguration rtConf = conf.getRuntimeConfiguration(); + + ActionConfig actionOne = rtConf.getActionConfig("", "actionOne"); + ActionConfig actionTwo = rtConf.getActionConfig("", "actionTwo"); + + List actionOneInterceptors = actionOne.getInterceptors(); + List actionTwoInterceptors = actionTwo.getInterceptors(); + + assertNotNull(actionOne); + assertNotNull(actionTwo); + assertNotNull(actionOneInterceptors); + assertNotNull(actionTwoInterceptors); + assertEquals(actionOneInterceptors.size(), 3); + assertEquals(actionTwoInterceptors.size(), 3); + + InterceptorMapping actionOneInterceptorMapping1 = actionOneInterceptors.get(0); + InterceptorMapping actionOneInterceptorMapping2 = actionOneInterceptors.get(1); + InterceptorMapping actionOneInterceptorMapping3 = actionOneInterceptors.get(2); + InterceptorMapping actionTwoInterceptorMapping1 = actionTwoInterceptors.get(0); + InterceptorMapping actionTwoInterceptorMapping2 = actionTwoInterceptors.get(1); + InterceptorMapping actionTwoInterceptorMapping3 = actionTwoInterceptors.get(2); + + assertNotNull(actionOneInterceptorMapping1); + assertNotNull(actionOneInterceptorMapping2); + assertNotNull(actionOneInterceptorMapping3); + assertNotNull(actionTwoInterceptorMapping1); + assertNotNull(actionTwoInterceptorMapping2); + assertNotNull(actionTwoInterceptorMapping3); + + assertEquals(((InterceptorForTestPurpose) actionOneInterceptorMapping1.getInterceptor()).getParamOne(), "i1p1"); + assertEquals(((InterceptorForTestPurpose) actionOneInterceptorMapping1.getInterceptor()).getParamTwo(), "i1p2"); + assertEquals(((InterceptorForTestPurpose) actionOneInterceptorMapping2.getInterceptor()).getParamOne(), "i2p1"); + assertEquals(((InterceptorForTestPurpose) actionOneInterceptorMapping2.getInterceptor()).getParamTwo(), null); + assertEquals(((InterceptorForTestPurpose) actionOneInterceptorMapping3.getInterceptor()).getParamOne(), null); + assertEquals(((InterceptorForTestPurpose) actionOneInterceptorMapping3.getInterceptor()).getParamTwo(), null); + + assertEquals(((InterceptorForTestPurpose) actionTwoInterceptorMapping1.getInterceptor()).getParamOne(), null); + assertEquals(((InterceptorForTestPurpose) actionTwoInterceptorMapping1.getInterceptor()).getParamTwo(), null); + assertEquals(((InterceptorForTestPurpose) actionTwoInterceptorMapping2.getInterceptor()).getParamOne(), null); + assertEquals(((InterceptorForTestPurpose) actionTwoInterceptorMapping2.getInterceptor()).getParamTwo(), "i2p2"); + assertEquals(((InterceptorForTestPurpose) actionTwoInterceptorMapping3.getInterceptor()).getParamOne(), "i3p1"); + assertEquals(((InterceptorForTestPurpose) actionTwoInterceptorMapping3.getInterceptor()).getParamTwo(), "i3p2"); + + } + + + @Override + protected void tearDown() throws Exception { + + configurationManager.clearContainerProviders(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorStackParamOverridingTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorStackParamOverridingTest.java new file mode 100644 index 000000000..a308787e0 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorStackParamOverridingTest.java @@ -0,0 +1,83 @@ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.RuntimeConfiguration; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.impl.DefaultConfiguration; + +import java.util.ArrayList; +import java.util.List; + +/** + * XmlConfigurationProviderInterceptorStackParamOverridingTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class XmlConfigurationProviderInterceptorStackParamOverridingTest extends XWorkTestCase { + + public void testInterceptorStackParamOveriding() throws Exception { + DefaultConfiguration conf = new DefaultConfiguration(); + final XmlConfigurationProvider p = new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-interceptor-stack-param-overriding.xml"); + configurationManager.addContainerProvider(p); + conf.reload(new ArrayList(){ + { + add(new XWorkConfigurationProvider()); + add(p); + } + }); + + + RuntimeConfiguration rtConf = conf.getRuntimeConfiguration(); + + ActionConfig actionOne = rtConf.getActionConfig("", "actionOne"); + ActionConfig actionTwo = rtConf.getActionConfig("", "actionTwo"); + + List actionOneInterceptors = actionOne.getInterceptors(); + List actionTwoInterceptors = actionTwo.getInterceptors(); + + assertNotNull(actionOne); + assertNotNull(actionTwo); + assertNotNull(actionOneInterceptors); + assertNotNull(actionTwoInterceptors); + assertEquals(actionOneInterceptors.size(), 3); + assertEquals(actionTwoInterceptors.size(), 3); + + InterceptorMapping actionOneInterceptorMapping1 = (InterceptorMapping) actionOneInterceptors.get(0); + InterceptorMapping actionOneInterceptorMapping2 = (InterceptorMapping) actionOneInterceptors.get(1); + InterceptorMapping actionOneInterceptorMapping3 = (InterceptorMapping) actionOneInterceptors.get(2); + InterceptorMapping actionTwoInterceptorMapping1 = (InterceptorMapping) actionTwoInterceptors.get(0); + InterceptorMapping actionTwoInterceptorMapping2 = (InterceptorMapping) actionTwoInterceptors.get(1); + InterceptorMapping actionTwoInterceptorMapping3 = (InterceptorMapping) actionTwoInterceptors.get(2); + + assertNotNull(actionOneInterceptorMapping1); + assertNotNull(actionOneInterceptorMapping2); + assertNotNull(actionOneInterceptorMapping3); + assertNotNull(actionTwoInterceptorMapping1); + assertNotNull(actionTwoInterceptorMapping2); + assertNotNull(actionTwoInterceptorMapping3); + + + assertEquals(((InterceptorForTestPurpose)actionOneInterceptorMapping1.getInterceptor()).getParamOne(), "i1p1"); + assertEquals(((InterceptorForTestPurpose)actionOneInterceptorMapping1.getInterceptor()).getParamTwo(), "i1p2"); + assertEquals(((InterceptorForTestPurpose)actionOneInterceptorMapping2.getInterceptor()).getParamOne(), "i2p1"); + assertEquals(((InterceptorForTestPurpose)actionOneInterceptorMapping2.getInterceptor()).getParamTwo(), null); + assertEquals(((InterceptorForTestPurpose)actionOneInterceptorMapping3.getInterceptor()).getParamOne(), null); + assertEquals(((InterceptorForTestPurpose)actionOneInterceptorMapping3.getInterceptor()).getParamTwo(), null); + + assertEquals(((InterceptorForTestPurpose)actionTwoInterceptorMapping1.getInterceptor()).getParamOne(), null); + assertEquals(((InterceptorForTestPurpose)actionTwoInterceptorMapping1.getInterceptor()).getParamTwo(), null); + assertEquals(((InterceptorForTestPurpose)actionTwoInterceptorMapping2.getInterceptor()).getParamOne(), null); + assertEquals(((InterceptorForTestPurpose)actionTwoInterceptorMapping2.getInterceptor()).getParamTwo(), "i2p2"); + assertEquals(((InterceptorForTestPurpose)actionTwoInterceptorMapping3.getInterceptor()).getParamOne(), "i3p1"); + assertEquals(((InterceptorForTestPurpose)actionTwoInterceptorMapping3.getInterceptor()).getParamTwo(), "i3p2"); + + } + + @Override + protected void tearDown() throws Exception { + configurationManager.clearContainerProviders(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorsSpringTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorsSpringTest.java new file mode 100644 index 000000000..de1a5f73d --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorsSpringTest.java @@ -0,0 +1,80 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.interceptor.TimerInterceptor; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.context.support.StaticApplicationContext; + +import java.util.Map; + + +/** + * Created by IntelliJ IDEA. + * User: Mike + * Date: May 6, 2003 + * Time: 3:10:16 PM + * To change this template use Options | File Templates. + */ +public class XmlConfigurationProviderInterceptorsSpringTest extends ConfigurationTestBase { + + InterceptorConfig timerInterceptor = new InterceptorConfig.Builder("timer", TimerInterceptor.class.getName()).build(); + ObjectFactory objectFactory; + StaticApplicationContext sac; + + + public void testInterceptorsLoadedFromSpringApplicationContext() throws ConfigurationException { + sac.registerSingleton("timer-interceptor", TimerInterceptor.class, new MutablePropertyValues()); + + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-interceptors-spring.xml"; + + // Expect a ConfigurationException to be thrown if the interceptor reference + // cannot be resolved + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map interceptorConfigs = pkg.getInterceptorConfigs(); + + // assertions for size + assertEquals(1, interceptorConfigs.size()); + + // assertions for interceptors + InterceptorConfig seen = (InterceptorConfig) interceptorConfigs.get("timer"); + assertEquals("timer-interceptor", seen.getClassName()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + sac = new StaticApplicationContext(); + + //SpringObjectFactory objFactory = new SpringObjectFactory(); + //objFactory.setApplicationContext(sac); + //ObjectFactory.setObjectFactory(objFactory); + + objectFactory = container.getInstance(ObjectFactory.class); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorsTest.java new file mode 100644 index 000000000..9ee55f852 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInterceptorsTest.java @@ -0,0 +1,224 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.RuntimeConfiguration; +import com.opensymphony.xwork2.config.entities.*; +import com.opensymphony.xwork2.interceptor.LoggingInterceptor; +import com.opensymphony.xwork2.interceptor.TimerInterceptor; +import com.opensymphony.xwork2.mock.MockInterceptor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + + +/** + * Created by IntelliJ IDEA. + * User: Mike + * Date: May 6, 2003 + * Time: 3:10:16 PM + * To change this template use Options | File Templates. + */ +public class XmlConfigurationProviderInterceptorsTest extends ConfigurationTestBase { + + InterceptorConfig loggingInterceptor = new InterceptorConfig.Builder("logging", LoggingInterceptor.class.getName()).build(); + InterceptorConfig mockInterceptor = new InterceptorConfig.Builder("mock", MockInterceptor.class.getName()).build(); + InterceptorConfig timerInterceptor = new InterceptorConfig.Builder("timer", TimerInterceptor.class.getName()).build(); + ObjectFactory objectFactory; + + @Override + public void setUp() throws Exception { + super.setUp(); + objectFactory = container.getInstance(ObjectFactory.class); + } + + + public void testBasicInterceptors() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-interceptors-basic.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // setup expectations + // the test interceptor with a parameter + Map params = new HashMap(); + params.put("foo", "expectedFoo"); + + InterceptorConfig paramsInterceptor = new InterceptorConfig.Builder("test", MockInterceptor.class.getName()) + .addParams(params).build(); + + // the default interceptor stack + InterceptorStackConfig defaultStack = new InterceptorStackConfig.Builder("defaultStack") + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .addInterceptor(new InterceptorMapping("test", objectFactory.buildInterceptor(mockInterceptor, params))) + .build(); + + // the derivative interceptor stack + InterceptorStackConfig derivativeStack = new InterceptorStackConfig.Builder("derivativeStack") + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .addInterceptor(new InterceptorMapping("test", objectFactory.buildInterceptor(mockInterceptor, params))) + .addInterceptor(new InterceptorMapping("logging", objectFactory.buildInterceptor(loggingInterceptor, new HashMap()))) + .build(); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map interceptorConfigs = pkg.getInterceptorConfigs(); + + // assertions for size + assertEquals(5, interceptorConfigs.size()); + + // assertions for interceptors + assertEquals(timerInterceptor, interceptorConfigs.get("timer")); + assertEquals(loggingInterceptor, interceptorConfigs.get("logging")); + assertEquals(paramsInterceptor, interceptorConfigs.get("test")); + + // assertions for interceptor stacks + assertEquals(defaultStack, interceptorConfigs.get("defaultStack")); + assertEquals(derivativeStack, interceptorConfigs.get("derivativeStack")); + } + + public void testInterceptorDefaultRefs() throws ConfigurationException { + loadConfigurationProviders(new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-interceptor-defaultref.xml")); + + // expectations - the inherited interceptor stack + // default package + ArrayList interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("logging", objectFactory.buildInterceptor(loggingInterceptor, new HashMap()))); + + ActionConfig actionWithOwnRef = new ActionConfig.Builder("", "ActionWithOwnRef", SimpleAction.class.getName()) + .addInterceptors(interceptors) + .build(); + + ActionConfig actionWithDefaultRef = new ActionConfig.Builder("", "ActionWithDefaultRef", SimpleAction.class.getName()) + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .build(); + + // sub package + // this should inherit + ActionConfig actionWithNoRef = new ActionConfig.Builder("", "ActionWithNoRef", SimpleAction.class.getName()) + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .build(); + + interceptors = new ArrayList(); + interceptors.add(new InterceptorMapping("logging", objectFactory.buildInterceptor(loggingInterceptor, new HashMap()))); + + ActionConfig anotherActionWithOwnRef = new ActionConfig.Builder("", "AnotherActionWithOwnRef", SimpleAction.class.getName()) + .addInterceptor(new InterceptorMapping("logging", objectFactory.buildInterceptor(loggingInterceptor, new HashMap()))) + .build(); + + RuntimeConfiguration runtimeConfig = configurationManager.getConfiguration().getRuntimeConfiguration(); + + // assertions + assertEquals(actionWithOwnRef, runtimeConfig.getActionConfig("", "ActionWithOwnRef")); + assertEquals(actionWithDefaultRef, runtimeConfig.getActionConfig("", "ActionWithDefaultRef")); + + assertEquals(actionWithNoRef, runtimeConfig.getActionConfig("", "ActionWithNoRef")); + assertEquals(anotherActionWithOwnRef, runtimeConfig.getActionConfig("", "AnotherActionWithOwnRef")); + } + + public void testInterceptorInheritance() throws ConfigurationException { + + // expectations - the inherited interceptor stack + InterceptorStackConfig inheritedStack = new InterceptorStackConfig.Builder("subDefaultStack") + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .build(); + + ConfigurationProvider provider = buildConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-interceptor-inheritance.xml"); + + // assertions + PackageConfig defaultPkg = configuration.getPackageConfig("default"); + assertEquals(2, defaultPkg.getInterceptorConfigs().size()); + + PackageConfig subPkg = configuration.getPackageConfig("subPackage"); + assertEquals(1, subPkg.getInterceptorConfigs().size()); + assertEquals(3, subPkg.getAllInterceptorConfigs().size()); + assertEquals(inheritedStack, subPkg.getInterceptorConfigs().get("subDefaultStack")); + + // expectations - the inherited interceptor stack + inheritedStack = new InterceptorStackConfig.Builder("subSubDefaultStack") + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .build(); + + PackageConfig subSubPkg = configuration.getPackageConfig("subSubPackage"); + assertEquals(1, subSubPkg.getInterceptorConfigs().size()); + assertEquals(4, subSubPkg.getAllInterceptorConfigs().size()); + assertEquals(inheritedStack, subSubPkg.getInterceptorConfigs().get("subSubDefaultStack")); + } + + + public void testInterceptorParamOverriding() throws Exception { + + Map params = new HashMap(); + params.put("foo", "expectedFoo"); + params.put("expectedFoo", "expectedFooValue"); + + InterceptorStackConfig defaultStack = new InterceptorStackConfig.Builder("defaultStack") + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .addInterceptor(new InterceptorMapping("test", objectFactory.buildInterceptor(mockInterceptor, params))) + .build(); + + ArrayList interceptors = new ArrayList(); + interceptors.addAll(defaultStack.getInterceptors()); + + ActionConfig intAction = new ActionConfig.Builder("", "TestInterceptorParam", SimpleAction.class.getName()) + .addInterceptors(interceptors) + .build(); + + // TestInterceptorParamOverride action tests that an interceptor with a param override worked + HashMap interceptorParams = new HashMap(); + interceptorParams.put("expectedFoo", "expectedFooValue2"); + interceptorParams.put("foo", "foo123"); + + InterceptorStackConfig defaultStack2 = new InterceptorStackConfig.Builder("defaultStack") + .addInterceptor(new InterceptorMapping("timer", objectFactory.buildInterceptor(timerInterceptor, new HashMap()))) + .addInterceptor(new InterceptorMapping("test", objectFactory.buildInterceptor(mockInterceptor, interceptorParams))) + .build(); + + interceptors = new ArrayList(); + + interceptors.addAll(defaultStack2.getInterceptors()); + + ActionConfig intOverAction = new ActionConfig.Builder("", "TestInterceptorParamOverride", SimpleAction.class.getName()) + .addInterceptors(interceptors) + .build(); + + ConfigurationProvider provider = buildConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-interceptor-params.xml"); + + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertEquals(2, actionConfigs.size()); + assertEquals(intAction, actionConfigs.get("TestInterceptorParam")); + assertEquals(intOverAction, actionConfigs.get("TestInterceptorParamOverride")); + + ActionConfig ac = (ActionConfig) actionConfigs.get("TestInterceptorParamOverride"); + assertEquals(defaultStack.getInterceptors(), ac.getInterceptors()); + + ActionConfig ac2 = (ActionConfig) actionConfigs.get("TestInterceptorParam"); + assertEquals(defaultStack2.getInterceptors(), ac2.getInterceptors()); + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInvalidFileTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInvalidFileTest.java new file mode 100644 index 000000000..889b2fd82 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderInvalidFileTest.java @@ -0,0 +1,40 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; + + +/** + * XmlConfigurationProviderInvalidFileTest + * + * @author Jason Carreira + * Created Sep 6, 2003 2:36:10 PM + */ +public class XmlConfigurationProviderInvalidFileTest extends ConfigurationTestBase { + + public void testInvalidFileThrowsException() { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-invalid-file.xml"; + + try { + ConfigurationProvider provider = buildConfigurationProvider(filename); + fail(); + } catch (ConfigurationException e) { + // this is what we expect + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderMultilevelTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderMultilevelTest.java new file mode 100644 index 000000000..dcf6b69fa --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderMultilevelTest.java @@ -0,0 +1,70 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.ActionChainResult; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.interceptor.ParametersInterceptor; +import junit.framework.Assert; + + +/** + * Verify that Interceptor inheritance is happy for multi-level package derivations + * + * @author $Author$ + * @version $Revision$ + */ +public class XmlConfigurationProviderMultilevelTest extends ConfigurationTestBase { + + /** + * attempt to load an xwork.xml file that has multilevel levels of inheritance and verify that the interceptors are + * correctly propagated through. + * + * @throws Exception + */ + public void testMultiLevelInheritance() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-multilevel.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + provider.init(configuration); + provider.loadPackages(); + + /** + * for this test, we expect the action named, action3, in the namespace, namespace3, to have a single + * ParameterInterceptor. The ParameterInterceptor, param, has been defined far up namespace3's parentage ... + * namespace3 -> namespace2 -> namespace1 -> default + */ + PackageConfig packageConfig = configuration.getPackageConfig("namespace3"); + Assert.assertNotNull(packageConfig); + assertEquals(2, packageConfig.getAllInterceptorConfigs().size()); + + ActionConfig actionConfig = packageConfig.getActionConfigs().get("action3"); + + assertNotNull(actionConfig); + assertNotNull(actionConfig.getInterceptors()); + assertEquals(2, actionConfig.getInterceptors().size()); + assertEquals(ParametersInterceptor.class, ((InterceptorMapping) actionConfig.getInterceptors().get(0)).getInterceptor().getClass()); + assertNotNull(actionConfig.getResults()); + assertEquals(1, actionConfig.getResults().size()); + assertTrue(actionConfig.getResults().containsKey("success")); + + ResultConfig resultConfig = (ResultConfig) actionConfig.getResults().get("success"); + assertEquals(ActionChainResult.class.getName(), resultConfig.getClassName()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java new file mode 100644 index 000000000..8b1d7dee0 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java @@ -0,0 +1,140 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.RuntimeConfiguration; +import com.opensymphony.xwork2.config.entities.PackageConfig; + +import java.util.List; + + +/** + * Created by IntelliJ IDEA. + * User: Mike + * Date: May 6, 2003 + * Time: 3:10:16 PM + * To change this template use Options | File Templates. + */ +public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase { + + public void testBadInheritance() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + provider.init(configuration); + provider.loadPackages(); + } + + public void testBasicPackages() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + provider.init(configuration); + provider.loadPackages(); + + // setup our expectations + PackageConfig expectedNamespacePackage = new PackageConfig.Builder("namespacepkg") + .namespace("/namespace/set") + .isAbstract(false) + .build(); + PackageConfig expectedAbstractPackage = new PackageConfig.Builder("abstractpkg") + .isAbstract(true) + .build(); + + // test expectations + assertEquals(3, configuration.getPackageConfigs().size()); + assertEquals(expectedNamespacePackage, configuration.getPackageConfig("namespacepkg")); + assertEquals(expectedAbstractPackage, configuration.getPackageConfig("abstractpkg")); + } + + public void testDefaultPackage() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + provider.init(configuration); + provider.loadPackages(); + + // setup our expectations + PackageConfig expectedPackageConfig = new PackageConfig.Builder("default").build(); + + // test expectations + assertEquals(1, configuration.getPackageConfigs().size()); + assertEquals(expectedPackageConfig, configuration.getPackageConfig("default")); + } + + public void testPackageInheritance() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + provider.init(configuration); + provider.loadPackages(); + + // test expectations + assertEquals(4, configuration.getPackageConfigs().size()); + PackageConfig defaultPackage = configuration.getPackageConfig("default"); + assertNotNull(defaultPackage); + assertEquals("default", defaultPackage.getName()); + PackageConfig abstractPackage = configuration.getPackageConfig("abstractPackage"); + assertNotNull(abstractPackage); + assertEquals("abstractPackage", abstractPackage.getName()); + PackageConfig singlePackage = configuration.getPackageConfig("singleInheritance"); + assertNotNull(singlePackage); + assertEquals("singleInheritance", singlePackage.getName()); + assertEquals(1, singlePackage.getParents().size()); + assertEquals(defaultPackage, singlePackage.getParents().get(0)); + PackageConfig multiplePackage = configuration.getPackageConfig("multipleInheritance"); + assertNotNull(multiplePackage); + assertEquals("multipleInheritance", multiplePackage.getName()); + assertEquals(3, multiplePackage.getParents().size()); + List multipleParents = multiplePackage.getParents(); + assertTrue(multipleParents.contains(defaultPackage)); + assertTrue(multipleParents.contains(abstractPackage)); + assertTrue(multipleParents.contains(singlePackage)); + + configurationManager.addConfigurationProvider(provider); + configurationManager.reload(); + + RuntimeConfiguration runtimeConfiguration = configurationManager.getConfiguration().getRuntimeConfiguration(); + assertNotNull(runtimeConfiguration.getActionConfig("/multiple", "default")); + assertNotNull(runtimeConfiguration.getActionConfig("/multiple", "abstract")); + assertNotNull(runtimeConfiguration.getActionConfig("/multiple", "single")); + assertNotNull(runtimeConfiguration.getActionConfig("/multiple", "multiple")); + assertNotNull(runtimeConfiguration.getActionConfig("/single", "default")); + assertNull(runtimeConfiguration.getActionConfig("/single", "abstract")); + assertNotNull(runtimeConfiguration.getActionConfig("/single", "single")); + assertNull(runtimeConfiguration.getActionConfig("/single", "multiple")); + + } + + public void testDefaultClassRef() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml"; + final String hasDefaultClassRefPkgName = "hasDefaultClassRef"; + final String noDefaultClassRefPkgName = "noDefaultClassRef"; + final String testDefaultClassRef = "com.opensymphony.xwork2.ActionSupport"; + + ConfigurationProvider provider = buildConfigurationProvider(filename); + provider.init(configuration); + + // setup our expectations + PackageConfig expectedDefaultClassRefPackage = new PackageConfig.Builder(hasDefaultClassRefPkgName).defaultClassRef(testDefaultClassRef).build(); + + PackageConfig expectedNoDefaultClassRefPackage = new PackageConfig.Builder(noDefaultClassRefPkgName).build(); + + // test expectations + assertEquals(2, configuration.getPackageConfigs().size()); + assertEquals(expectedDefaultClassRefPackage, configuration.getPackageConfig(hasDefaultClassRefPkgName)); + assertEquals(expectedNoDefaultClassRefPackage, configuration.getPackageConfig(noDefaultClassRefPkgName)); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultTypesTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultTypesTest.java new file mode 100644 index 000000000..f5ef9356f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultTypesTest.java @@ -0,0 +1,119 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.mock.MockResult; + +import java.util.Map; + + +/** + * Test XmlConfigurationProvider's ... + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class XmlConfigurationProviderResultTypesTest extends ConfigurationTestBase { + + public void testPlainResultTypesParams() throws Exception { + ConfigurationProvider configurationProvider = buildConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-result-types.xml"); + + PackageConfig packageConfig = configuration.getPackageConfig("xworkResultTypesTestPackage1"); + Map resultTypesConfigMap = packageConfig.getResultTypeConfigs(); + + assertEquals(resultTypesConfigMap.size(), 2); + assertTrue(resultTypesConfigMap.containsKey("result1")); + assertTrue(resultTypesConfigMap.containsKey("result2")); + assertFalse(resultTypesConfigMap.containsKey("result3")); + + ResultTypeConfig result1ResultTypeConfig = (ResultTypeConfig) resultTypesConfigMap.get("result1"); + Map result1ParamsMap = result1ResultTypeConfig.getParams(); + ResultTypeConfig result2ResultTypeConfig = (ResultTypeConfig) resultTypesConfigMap.get("result2"); + Map result2ParamsMap = result2ResultTypeConfig.getParams(); + + assertEquals(result1ResultTypeConfig.getName(), "result1"); + assertEquals(result1ResultTypeConfig.getClazz(), MockResult.class.getName()); + assertEquals(result2ResultTypeConfig.getName(), "result2"); + assertEquals(result2ResultTypeConfig.getClazz(), MockResult.class.getName()); + assertEquals(result1ParamsMap.size(), 3); + assertEquals(result2ParamsMap.size(), 2); + assertTrue(result1ParamsMap.containsKey("param1")); + assertTrue(result1ParamsMap.containsKey("param2")); + assertTrue(result1ParamsMap.containsKey("param3")); + assertFalse(result1ParamsMap.containsKey("param4")); + assertTrue(result2ParamsMap.containsKey("paramA")); + assertTrue(result2ParamsMap.containsKey("paramB")); + assertFalse(result2ParamsMap.containsKey("paramC")); + assertEquals(result1ParamsMap.get("param1"), "value1"); + assertEquals(result1ParamsMap.get("param2"), "value2"); + assertEquals(result1ParamsMap.get("param3"), "value3"); + assertEquals(result2ParamsMap.get("paramA"), "valueA"); + assertEquals(result2ParamsMap.get("paramB"), "valueB"); + } + + public void testInheritedResultTypesParams() throws Exception { + ConfigurationProvider configurationProvider = buildConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork-test-result-types.xml"); + + PackageConfig packageConfig = configuration.getPackageConfig("xworkResultTypesTestPackage2"); + Map actionConfigMap = packageConfig.getActionConfigs(); + + + ActionConfig action1ActionConfig = (ActionConfig) actionConfigMap.get("action1"); + ActionConfig action2ActionConfig = (ActionConfig) actionConfigMap.get("action2"); + + ResultConfig action1Result = (ResultConfig) action1ActionConfig.getResults().get("success"); + ResultConfig action2Result = (ResultConfig) action2ActionConfig.getResults().get("success"); + + assertEquals(action1Result.getName(), "success"); + assertEquals(action1Result.getClassName(), "com.opensymphony.xwork2.mock.MockResult"); + assertEquals(action1Result.getName(), "success"); + assertEquals(action1Result.getClassName(), "com.opensymphony.xwork2.mock.MockResult"); + + Map action1ResultMap = action1Result.getParams(); + Map action2ResultMap = action2Result.getParams(); + + assertEquals(action1ResultMap.size(), 5); + assertTrue(action1ResultMap.containsKey("param1")); + assertTrue(action1ResultMap.containsKey("param2")); + assertTrue(action1ResultMap.containsKey("param3")); + assertTrue(action1ResultMap.containsKey("param10")); + assertTrue(action1ResultMap.containsKey("param11")); + assertFalse(action1ResultMap.containsKey("param12")); + assertEquals(action1ResultMap.get("param1"), "newValue1"); + assertEquals(action1ResultMap.get("param2"), "value2"); + assertEquals(action1ResultMap.get("param3"), "newValue3"); + assertEquals(action1ResultMap.get("param10"), "value10"); + assertEquals(action1ResultMap.get("param11"), "value11"); + + assertEquals(action2ResultMap.size(), 3); + assertTrue(action2ResultMap.containsKey("paramA")); + assertTrue(action2ResultMap.containsKey("paramB")); + assertTrue(action2ResultMap.containsKey("paramZ")); + assertFalse(action2ResultMap.containsKey("paramY")); + assertEquals(action2ResultMap.get("paramA"), "valueA"); + assertEquals(action2ResultMap.get("paramB"), "newValueB"); + assertEquals(action2ResultMap.get("paramZ"), "valueZ"); + + + } +} + + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java new file mode 100644 index 000000000..468b8d2de --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderResultsTest.java @@ -0,0 +1,121 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.ActionChainResult; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.mock.MockResult; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Created by IntelliJ IDEA. + * User: Mike + * Date: May 6, 2003 + * Time: 3:10:16 PM + * To change this template use Options | File Templates. + */ +public class XmlConfigurationProviderResultsTest extends ConfigurationTestBase { + + public void testActions() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-results.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + HashMap parameters = new HashMap(); + HashMap results = new HashMap(); + + results.put("chainDefaultTypedResult", new ResultConfig.Builder("chainDefaultTypedResult", ActionChainResult.class.getName()).build()); + + results.put("mockTypedResult", new ResultConfig.Builder("mockTypedResult", MockResult.class.getName()).build()); + + Map resultParams = new HashMap(); + resultParams.put("actionName", "bar.vm"); + results.put("specificLocationResult", new ResultConfig.Builder("specificLocationResult", ActionChainResult.class.getName()) + .addParams(resultParams).build()); + + resultParams = new HashMap(); + resultParams.put("actionName", "foo.vm"); + results.put("defaultLocationResult", new ResultConfig.Builder("defaultLocationResult", ActionChainResult.class.getName()) + .addParams(resultParams).build()); + + resultParams = new HashMap(); + resultParams.put("foo", "bar"); + results.put("noDefaultLocationResult", new ResultConfig.Builder("noDefaultLocationResult", ActionChainResult.class.getName()) + .addParams(resultParams).build()); + + ActionConfig expectedAction = new ActionConfig.Builder("default", "Bar", SimpleAction.class.getName()) + .addParams(parameters) + .addResultConfigs(results) + .build(); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map actionConfigs = pkg.getActionConfigs(); + + // assertions + assertEquals(1, actionConfigs.size()); + + ActionConfig action = actionConfigs.get("Bar"); + assertEquals(expectedAction, action); + } + + public void testResultInheritance() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-result-inheritance.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // expectations + provider.init(configuration); + provider.loadPackages(); + + // assertions + PackageConfig subPkg = configuration.getPackageConfig("subPackage"); + assertEquals(1, subPkg.getResultTypeConfigs().size()); + assertEquals(3, subPkg.getAllResultTypeConfigs().size()); + } + + public void testResultTypes() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-results.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + // setup expectations + ResultTypeConfig chainResult = new ResultTypeConfig.Builder("chain", ActionChainResult.class.getName()).build(); + ResultTypeConfig mockResult = new ResultTypeConfig.Builder("mock", MockResult.class.getName()).build(); + + // execute the configuration + provider.init(configuration); + provider.loadPackages(); + + PackageConfig pkg = configuration.getPackageConfig("default"); + Map resultTypes = pkg.getResultTypeConfigs(); + + // assertions + assertEquals(2, resultTypes.size()); + assertEquals("chain", pkg.getDefaultResultType()); + assertEquals(chainResult, resultTypes.get("chain")); + assertEquals(mockResult, resultTypes.get("mock")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java new file mode 100644 index 000000000..a85367e2b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java @@ -0,0 +1,188 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.RuntimeConfiguration; +import com.opensymphony.xwork2.config.impl.MockConfiguration; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.ObjectFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +import org.w3c.dom.Document; + + +public class XmlConfigurationProviderTest extends ConfigurationTestBase { + + public void testLoadOrder() throws Exception { + configuration = new MockConfiguration(); + ((MockConfiguration)configuration).selfRegister(); + container = configuration.getContainer(); + + XmlConfigurationProvider prov = new XmlConfigurationProvider("xwork-test-load-order.xml", true) { + @Override + protected Iterator getConfigurationUrls(String fileName) throws IOException { + List urls = new ArrayList(); + urls.add(ClassLoaderUtil.getResource("com/opensymphony/xwork2/config/providers/loadorder1/xwork-test-load-order.xml", XmlConfigurationProvider.class)); + urls.add(ClassLoaderUtil.getResource("com/opensymphony/xwork2/config/providers/loadorder2/xwork-test-load-order.xml", XmlConfigurationProvider.class)); + urls.add(ClassLoaderUtil.getResource("com/opensymphony/xwork2/config/providers/loadorder3/xwork-test-load-order.xml", XmlConfigurationProvider.class)); + return urls.iterator(); + } + }; + prov.setObjectFactory(container.getInstance(ObjectFactory.class)); + prov.init(configuration); + List docs = prov.getDocuments(); + assertEquals(3, docs.size() ); + + assertEquals(1, XmlHelper.getLoadOrder(docs.get(0)).intValue()); + assertEquals(2, XmlHelper.getLoadOrder(docs.get(1)).intValue()); + assertEquals(3, XmlHelper.getLoadOrder(docs.get(2)).intValue()); + } + + public void testNeedsReload() throws Exception { + FileManager.setReloadingConfigs(true); + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-actions.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + assertTrue(!provider.needsReload()); + + File file = new File(getClass().getResource("/"+filename).getFile()); + assertTrue(file.exists()); + file.setLastModified(System.currentTimeMillis()); + + assertTrue(provider.needsReload()); + } + + public void testInheritence() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-include-parent.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + provider.init(configuration); + provider.loadPackages(); + + // test expectations + assertEquals(6, configuration.getPackageConfigs().size()); + + + PackageConfig defaultPackage = configuration.getPackageConfig("default"); + assertNotNull(defaultPackage); + assertEquals("default", defaultPackage.getName()); + + + PackageConfig namespace1 = configuration.getPackageConfig("namespace1"); + assertNotNull(namespace1); + assertEquals("namespace1", namespace1.getName()); + assertEquals(defaultPackage, namespace1.getParents().get(0)); + + PackageConfig namespace2 = configuration.getPackageConfig("namespace2"); + assertNotNull(namespace2); + assertEquals("namespace2", namespace2.getName()); + assertEquals(1, namespace2.getParents().size()); + assertEquals(namespace1, namespace2.getParents().get(0)); + + + PackageConfig namespace4 = configuration.getPackageConfig("namespace4"); + assertNotNull(namespace4); + assertEquals("namespace4", namespace4.getName()); + assertEquals(1, namespace4.getParents().size()); + assertEquals(namespace1, namespace4.getParents().get(0)); + + + PackageConfig namespace5 = configuration.getPackageConfig("namespace5"); + assertNotNull(namespace5); + assertEquals("namespace5", namespace5.getName()); + assertEquals(1, namespace5.getParents().size()); + assertEquals(namespace4, namespace5.getParents().get(0)); + + configurationManager.addConfigurationProvider(provider); + configurationManager.reload(); + + RuntimeConfiguration runtimeConfiguration = configurationManager.getConfiguration().getRuntimeConfiguration(); + assertNotNull(runtimeConfiguration.getActionConfig("/namespace1", "action1")); + assertNotNull(runtimeConfiguration.getActionConfig("/namespace2", "action2")); + assertNotNull(runtimeConfiguration.getActionConfig("/namespace4", "action4")); + assertNotNull(runtimeConfiguration.getActionConfig("/namespace5", "action5")); + } + + public void testGuessResultType() { + XmlConfigurationProvider prov = new XmlConfigurationProvider(); + + assertEquals(null, prov.guessResultType(null)); + assertEquals("foo", prov.guessResultType("foo")); + assertEquals("foo", prov.guessResultType("foo-")); + assertEquals("fooBar", prov.guessResultType("foo-bar")); + assertEquals("fooBarBaz", prov.guessResultType("foo-bar-baz")); + } + + public void testEmptySpaces() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork- test.xml"; + FileManager.setReloadingConfigs(true); + + ConfigurationProvider provider = buildConfigurationProvider(filename); + assertTrue(!provider.needsReload()); + + URI uri = ClassLoaderUtil.getResource(filename, ConfigurationProvider.class).toURI(); + + File file = new File(uri); + + assertTrue(file.exists()); + file.setLastModified(System.currentTimeMillis()); + + assertTrue(provider.needsReload()); + } + + public void testConfigsInJarFiles() throws Exception { + FileManager.setReloadingConfigs(true); + testProvider("xwork-jar.xml"); + testProvider("xwork-zip.xml"); + testProvider("xwork - jar.xml"); + testProvider("xwork - zip.xml"); + + testProvider("xwork-jar2.xml"); + testProvider("xwork-zip2.xml"); + testProvider("xwork - jar2.xml"); + testProvider("xwork - zip2.xml"); + } + + private void testProvider(String configFile) throws Exception { + ConfigurationProvider provider = buildConfigurationProvider(configFile); + assertTrue(!provider.needsReload()); + + String fullPath = ClassLoaderUtil.getResource(configFile, ConfigurationProvider.class).toString(); + + int startIndex = fullPath.indexOf(":file:/"); + int endIndex = fullPath.indexOf("!/"); + + String jar = fullPath.substring(startIndex + (":file:/".length() -1 ), endIndex).replaceAll("%20", " "); + + File file = new File(jar); + + assertTrue("File [" + file + "] doesn't exist!", file.exists()); + file.setLastModified(System.currentTimeMillis()); + + assertTrue(!provider.needsReload()); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderUnknownHandlerStackTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderUnknownHandlerStackTest.java new file mode 100644 index 000000000..ba1758888 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderUnknownHandlerStackTest.java @@ -0,0 +1,40 @@ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.UnknownHandlerManager; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; +import com.opensymphony.xwork2.DefaultUnknownHandlerManager; + +import java.util.List; + +public class XmlConfigurationProviderUnknownHandlerStackTest extends ConfigurationTestBase { + + public void testStackWithElements() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + loadConfigurationProviders(provider); + configurationManager.reload(); + + List unknownHandlerStack = configuration.getUnknownHandlerStack(); + assertNotNull(unknownHandlerStack); + assertEquals(2, unknownHandlerStack.size()); + + assertEquals("uh1", unknownHandlerStack.get(0).getName()); + assertEquals("uh2", unknownHandlerStack.get(1).getName()); + + UnknownHandlerManager unknownHandlerManager = new DefaultUnknownHandlerManager(); + container.inject(unknownHandlerManager); + assertTrue(unknownHandlerManager.hasUnknownHandlers()); + } + + public void testEmptyStack() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack-empty.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + loadConfigurationProviders(provider); + configurationManager.reload(); + + List unknownHandlerStack = configuration.getUnknownHandlerStack(); + assertNull(unknownHandlerStack); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderWildCardIncludeTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderWildCardIncludeTest.java new file mode 100644 index 000000000..45095b3be --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderWildCardIncludeTest.java @@ -0,0 +1,48 @@ +/* + * 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.providers; + +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.PackageConfig; + +public class XmlConfigurationProviderWildCardIncludeTest extends ConfigurationTestBase { + + + public void testWildCardInclude() throws Exception { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-wildcard-include.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + + provider.init(configuration); + provider.loadPackages(); + + PackageConfig defaultWildcardPackage = configuration.getPackageConfig("default-wildcard"); + assertNotNull(defaultWildcardPackage); + assertEquals("default-wildcard", defaultWildcardPackage.getName()); + + + PackageConfig defaultOnePackage = configuration.getPackageConfig("default-1"); + assertNotNull(defaultOnePackage); + assertEquals("default-1", defaultOnePackage.getName()); + + PackageConfig defaultTwoPackage = configuration.getPackageConfig("default-2"); + assertNotNull(defaultTwoPackage); + assertEquals("default-2", defaultTwoPackage.getName()); + + configurationManager.addConfigurationProvider(provider); + configurationManager.reload(); + + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlHelperTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlHelperTest.java new file mode 100644 index 000000000..9e0f1a39e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlHelperTest.java @@ -0,0 +1,255 @@ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.XWorkTestCase; +import org.easymock.MockControl; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.Map; + +/** + * XmlHelperTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class XmlHelperTest extends XWorkTestCase { + + public void testGetContent1() throws Exception { + // set up Node + MockControl nodeControl = MockControl.createControl(Node.class); + Node mockNode = (Node) nodeControl.getMock(); + + nodeControl.expectAndDefaultReturn(mockNode.getNodeValue(), "testing testing 123"); + nodeControl.expectAndDefaultReturn(mockNode.getNodeType(), Node.TEXT_NODE); + + + // set up NodeList + MockControl nodeListControl = MockControl.createControl(NodeList.class); + NodeList mockNodeList = (NodeList) nodeListControl.getMock(); + + nodeListControl.expectAndDefaultReturn(mockNodeList.getLength(), 1); + nodeListControl.expectAndDefaultReturn(mockNodeList.item(0), mockNode); + + + // set up Element + MockControl elementControl = MockControl.createControl(Element.class); + Element mockElement = (Element) elementControl.getMock(); + + elementControl.expectAndDefaultReturn(mockElement.getChildNodes(), mockNodeList); + + nodeControl.replay(); + nodeListControl.replay(); + elementControl.replay(); + + String result = XmlHelper.getContent(mockElement); + + nodeControl.verify(); + nodeListControl.verify(); + elementControl.verify(); + + assertEquals(result, "testing testing 123"); + } + + + public void testGetContent2() throws Exception { + // set up Node + MockControl nodeControl1 = MockControl.createControl(Node.class); + Node mockNode1 = (Node) nodeControl1.getMock(); + + nodeControl1.expectAndDefaultReturn(mockNode1.getNodeValue(), "testing testing 123"); + nodeControl1.expectAndDefaultReturn(mockNode1.getNodeType(), Node.TEXT_NODE); + + MockControl nodeControl2 = MockControl.createControl(Node.class); + Node mockNode2 = (Node) nodeControl2.getMock(); + + nodeControl2.expectAndDefaultReturn(mockNode2.getNodeValue(), "comment 1"); + nodeControl2.expectAndDefaultReturn(mockNode2.getNodeType(), Node.COMMENT_NODE); + + MockControl nodeControl3 = MockControl.createControl(Node.class); + Node mockNode3 = (Node) nodeControl3.getMock(); + + nodeControl3.expectAndDefaultReturn(mockNode3.getNodeValue(), " tmjee "); + nodeControl3.expectAndDefaultReturn(mockNode3.getNodeType(), Node.TEXT_NODE); + + MockControl nodeControl4 = MockControl.createControl(Node.class); + Node mockNode4 = (Node) nodeControl4.getMock(); + + nodeControl4.expectAndDefaultReturn(mockNode4.getNodeValue(), " phil "); + nodeControl4.expectAndDefaultReturn(mockNode4.getNodeType(), Node.TEXT_NODE); + + MockControl nodeControl5 = MockControl.createControl(Node.class); + Node mockNode5 = (Node) nodeControl5.getMock(); + + nodeControl5.expectAndDefaultReturn(mockNode5.getNodeValue(), "comment 2"); + nodeControl5.expectAndDefaultReturn(mockNode5.getNodeType(), Node.COMMENT_NODE); + + MockControl nodeControl6 = MockControl.createControl(Node.class); + Node mockNode6 = (Node) nodeControl6.getMock(); + + nodeControl6.expectAndDefaultReturn(mockNode6.getNodeValue(), "comment 3"); + nodeControl6.expectAndDefaultReturn(mockNode6.getNodeType(), Node.COMMENT_NODE); + + + // set up NodeList + MockControl nodeListControl = MockControl.createControl(NodeList.class); + NodeList mockNodeList = (NodeList) nodeListControl.getMock(); + + nodeListControl.expectAndDefaultReturn(mockNodeList.getLength(), 6); + mockNodeList.item(0); + nodeListControl.setReturnValue(mockNode1); + mockNodeList.item(1); + nodeListControl.setReturnValue(mockNode2); + mockNodeList.item(2); + nodeListControl.setDefaultReturnValue(mockNode3); + mockNodeList.item(3); + nodeListControl.setReturnValue(mockNode4); + mockNodeList.item(4); + nodeListControl.setReturnValue(mockNode5); + mockNodeList.item(5); + nodeListControl.setReturnValue(mockNode6); + + + // set up Element + MockControl elementControl = MockControl.createControl(Element.class); + Element mockElement = (Element) elementControl.getMock(); + + elementControl.expectAndDefaultReturn(mockElement.getChildNodes(), mockNodeList); + + nodeControl1.replay(); + nodeControl2.replay(); + nodeControl3.replay(); + nodeControl4.replay(); + nodeControl5.replay(); + nodeControl6.replay(); + nodeListControl.replay(); + elementControl.replay(); + + String result = XmlHelper.getContent(mockElement); + + nodeControl1.verify(); + nodeControl2.verify(); + nodeControl3.verify(); + nodeControl4.verify(); + nodeControl5.verify(); + nodeControl6.verify(); + nodeListControl.verify(); + elementControl.verify(); + + assertEquals(result, "testing testing 123tmjeephil"); + } + + + + public void testGetParams() throws Exception { + // value1 + MockControl nodeControl1 = MockControl.createControl(Node.class); + Node mockNode1 = (Node) nodeControl1.getMock(); + + nodeControl1.expectAndDefaultReturn(mockNode1.getNodeValue(), "value1"); + nodeControl1.expectAndDefaultReturn(mockNode1.getNodeType(), Node.TEXT_NODE); + + + MockControl nodeListControl1 = MockControl.createControl(NodeList.class); + NodeList mockNodeList1 = (NodeList) nodeListControl1.getMock(); + + nodeListControl1.expectAndDefaultReturn(mockNodeList1.getLength(), 1); + nodeListControl1.expectAndDefaultReturn(mockNodeList1.item(0), mockNode1); + + MockControl paramControl1 = MockControl.createControl(Element.class); + Element mockParamElement1 = (Element) paramControl1.getMock(); + mockParamElement1.getNodeName(); + paramControl1.setReturnValue("param"); + + mockParamElement1.getNodeType(); + paramControl1.setReturnValue(Node.ELEMENT_NODE); + + mockParamElement1.getAttribute("name"); + paramControl1.setReturnValue("param1"); + + mockParamElement1.getChildNodes(); + paramControl1.setReturnValue(mockNodeList1); + + nodeControl1.replay(); + nodeListControl1.replay(); + paramControl1.replay(); + + // value2 + MockControl nodeControl2 = MockControl.createControl(Node.class); + Node mockNode2 = (Node) nodeControl2.getMock(); + + nodeControl2.expectAndDefaultReturn(mockNode2.getNodeValue(), "value2"); + nodeControl2.expectAndDefaultReturn(mockNode2.getNodeType(), Node.TEXT_NODE); + + + MockControl nodeListControl2 = MockControl.createControl(NodeList.class); + NodeList mockNodeList2 = (NodeList) nodeListControl2.getMock(); + + nodeListControl2.expectAndDefaultReturn(mockNodeList2.getLength(), 1); + nodeListControl2.expectAndDefaultReturn(mockNodeList2.item(0), mockNode2); + + MockControl paramControl2 = MockControl.createControl(Element.class); + Element mockParamElement2 = (Element) paramControl2.getMock(); + mockParamElement2.getNodeName(); + paramControl2.setReturnValue("param"); + + mockParamElement2.getNodeType(); + paramControl2.setReturnValue(Node.ELEMENT_NODE); + + mockParamElement2.getAttribute("name"); + paramControl2.setReturnValue("param2"); + + mockParamElement2.getChildNodes(); + paramControl2.setReturnValue(mockNodeList2); + + nodeControl2.replay(); + nodeListControl2.replay(); + paramControl2.replay(); + + + // + // ... + // + MockControl elementNodeListControl = MockControl.createControl(NodeList.class); + NodeList mockElementNodeList = (NodeList) elementNodeListControl.getMock(); + + elementNodeListControl.expectAndDefaultReturn(mockElementNodeList.getLength(), 2); + mockElementNodeList.item(0); + elementNodeListControl.setReturnValue(mockParamElement2); + mockElementNodeList.item(1); + elementNodeListControl.setReturnValue(mockParamElement1); + + MockControl elementControl = MockControl.createControl(Element.class); + Element element = (Element) elementControl.getMock(); + + elementControl.expectAndDefaultReturn(element.getChildNodes(), mockElementNodeList); + + + elementNodeListControl.replay(); + elementControl.replay(); + + + + Map params = XmlHelper.getParams(element); + + nodeControl1.verify(); + nodeListControl1.verify(); + paramControl1.verify(); + + + nodeControl2.verify(); + nodeListControl2.verify(); + paramControl2.verify(); + + + elementNodeListControl.verify(); + elementControl.verify(); + + + assertNotNull(params); + assertEquals(params.size(), 2); + assertEquals(params.get("param1"), "value1"); + assertEquals(params.get("param2"), "value2"); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/ConversionTestAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/ConversionTestAction.java new file mode 100644 index 000000000..de743fe39 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/ConversionTestAction.java @@ -0,0 +1,97 @@ +/* + * 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.conversion; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.conversion.annotations.Conversion; +import com.opensymphony.xwork2.conversion.annotations.ConversionRule; +import com.opensymphony.xwork2.conversion.annotations.ConversionType; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; + +import java.util.HashMap; +import java.util.List; + +/** + * ConversionTestAction + * + * @author Rainer Hermanns + * @version $Id$ + */ +@Conversion() +public class ConversionTestAction implements Action { + + + + private String convertInt; + + private String convertDouble; + + private List users = null; + + + private HashMap keyValues = null; + + + public String getConvertInt() { + return convertInt; + } + + @TypeConversion(type = ConversionType.APPLICATION, converter = "com.opensymphony.xwork2.util.XWorkBasicConverter") + public void setConvertInt( String convertInt ) { + this.convertInt = convertInt; + } + + public String getConvertDouble() { + return convertDouble; + } + + @TypeConversion(converter = "com.opensymphony.xwork2.util.XWorkBasicConverter") + public void setConvertDouble( String convertDouble ) { + this.convertDouble = convertDouble; + } + + public List getUsers() { + return users; + } + + @TypeConversion(rule = ConversionRule.COLLECTION, converter = "java.lang.String") + public void setUsers( List users ) { + this.users = users; + } + + public HashMap getKeyValues() { + return keyValues; + } + + @TypeConversion(rule = ConversionRule.MAP, converter = "java.math.BigInteger") + public void setKeyValues( HashMap keyValues ) { + this.keyValues = keyValues; + } + + /** + * Where the logic of the action is executed. + * + * @return a string representing the logical result of the execution. + * See constants in this interface for a list of standard result values. + * @throws Exception thrown if a system level exception occurs. + * Application level exceptions should be handled by returning + * an error value, such as Action.ERROR. + */ + @TypeConversion(type = ConversionType.APPLICATION, key = "java.util.Date", converter = "com.opensymphony.xwork2.util.XWorkBasicConverter") + public String execute() throws Exception { + return SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverterTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverterTest.java new file mode 100644 index 000000000..4a7f5175b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverterTest.java @@ -0,0 +1,471 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.test.AnnotationUser; +import com.opensymphony.xwork2.test.ModelDrivenAnnotationAction2; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.OgnlException; +import ognl.OgnlRuntime; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + + +/** + * @author $Author$ + * @author Rainer Hermanns + * @version $Revision$ + */ +public class AnnotationXWorkConverterTest extends XWorkTestCase { + + ActionContext ac; + Map context; + XWorkConverter converter; + +// public void testConversionToSetKeepsOriginalSetAndReplacesContents() { +// ValueStack stack = ValueStackFactory.getFactory().createValueStack(); +// +// Map stackContext = stack.getContext(); +// stackContext.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.TRUE); +// stackContext.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.TRUE); +// stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); +// +// String[] param = new String[] {"abc", "def", "ghi"}; +// List paramList = Arrays.asList(param); +// +// List originalList = new ArrayList(); +// originalList.add("jkl"); +// originalList.add("mno"); +// +// AnnotationUser user = new AnnotationUser(); +// user.setList(originalList); +// stack.push(user); +// +// stack.setValue("list", param); +// +// List userList = user.getList(); +// assertEquals(3,userList.size()); +// assertEquals(paramList,userList); +// assertSame(originalList,userList); +// } + + public void testArrayToNumberConversion() { + String[] value = new String[]{"12345"}; + assertEquals(new Integer(12345), converter.convertValue(context, null, null, null, value, Integer.class)); + assertEquals(new Long(12345), converter.convertValue(context, null, null, null, value, Long.class)); + value[0] = "123.45"; + assertEquals(new Float(123.45), converter.convertValue(context, null, null, null, value, Float.class)); + assertEquals(new Double(123.45), converter.convertValue(context, null, null, null, value, Double.class)); + value[0] = "1234567890123456789012345678901234567890"; + assertEquals(new BigInteger(value[0]), converter.convertValue(context, null, null, null, value, BigInteger.class)); + value[0] = "1234567890123456789.012345678901234567890"; + assertEquals(new BigDecimal(value[0]), converter.convertValue(context, null, null, null, value, BigDecimal.class)); + } + + public void testDateConversion() throws ParseException { + java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis()); + assertEquals(sqlDate, converter.convertValue(context, null, null, null, sqlDate, Date.class)); + + SimpleDateFormat format = new SimpleDateFormat("mm/dd/yyyy hh:mm:ss"); + Date date = format.parse("01/10/2001 00:00:00"); + String dateStr = (String) converter.convertValue(context, null, null, null, date, String.class); + Date date2 = (Date) converter.convertValue(context, null, null, null, dateStr, Date.class); + assertEquals(date, date2); + } + + public void testFieldErrorMessageAddedForComplexProperty() { + SimpleAnnotationAction action = new SimpleAnnotationAction(); + action.setBean(new AnnotatedTestBean()); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(action); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + ognlStackContext.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, "bean.birth"); + + String[] value = new String[]{"invalid date"}; + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action.getBean(), null, "birth", value, Date.class)); + stack.pop(); + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertTrue(conversionErrors.size() == 1); + assertEquals(value, conversionErrors.get("bean.birth")); + } + + public void testFieldErrorMessageAddedWhenConversionFails() { + SimpleAnnotationAction action = new SimpleAnnotationAction(); + action.setDate(null); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(action); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + String[] value = new String[]{"invalid date"}; + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action, null, "date", value, Date.class)); + stack.pop(); + + Map conversionErrors = (Map) ognlStackContext.get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertEquals(1, conversionErrors.size()); + assertNotNull(conversionErrors.get("date")); + assertEquals(value, conversionErrors.get("date")); + } + + public void testFieldErrorMessageAddedWhenConversionFailsOnModelDriven() { + ModelDrivenAnnotationAction action = new ModelDrivenAnnotationAction(); + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(action); + stack.push(action.getModel()); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + String[] value = new String[]{"invalid date"}; + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action, null, "birth", value, Date.class)); + stack.pop(); + stack.pop(); + + Map conversionErrors = (Map) ognlStackContext.get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertEquals(1, conversionErrors.size()); + assertNotNull(conversionErrors.get("birth")); + assertEquals(value, conversionErrors.get("birth")); + } + + public void testFindConversionErrorMessage() { + ModelDrivenAnnotationAction action = new ModelDrivenAnnotationAction(); + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(action); + stack.push(action.getModel()); + + String message = XWorkConverter.getConversionErrorMessage("birth", stack); + assertNotNull(message); + assertEquals("Invalid date for birth.", message); + + message = XWorkConverter.getConversionErrorMessage("foo", stack); + assertNotNull(message); + assertEquals("Invalid field value for field \"foo\".", message); + } + + public void testFindConversionMappingForInterface() { + ModelDrivenAnnotationAction2 action = new ModelDrivenAnnotationAction2(); + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(action); + stack.push(action.getModel()); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + String value = "asdf:123"; + Object o = converter.convertValue(ognlStackContext, action.getModel(), null, "barObj", value, Bar.class); + assertNotNull(o); + assertTrue("class is: " + o.getClass(), o instanceof Bar); + + Bar b = (Bar) o; + assertEquals(value, b.getTitle() + ":" + b.getSomethingElse()); + } + + public void testLocalizedDateConversion() throws Exception { + Date date = new Date(System.currentTimeMillis()); + Locale locale = Locale.GERMANY; + DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); + String dateString = df.format(date); + context.put(ActionContext.LOCALE, locale); + assertEquals(dateString, converter.convertValue(context, null, null, null, date, String.class)); + } + + public void testStringArrayToCollection() { + List list = new ArrayList(); + list.add("foo"); + list.add("bar"); + list.add("baz"); + assertEquals(list, converter.convertValue(context, null, null, null, new String[]{ + "foo", "bar", "baz" + }, Collection.class)); + } + + public void testStringArrayToList() { + List list = new ArrayList(); + list.add("foo"); + list.add("bar"); + list.add("baz"); + assertEquals(list, converter.convertValue(context, null, null, null, new String[]{ + "foo", "bar", "baz" + }, List.class)); + } + + public void testStringArrayToPrimitiveWrappers() { + Long[] longs = (Long[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Long[].class); + assertNotNull(longs); + assertTrue(Arrays.equals(new Long[]{new Long(123), new Long(456)}, longs)); + + Integer[] ints = (Integer[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Integer[].class); + assertNotNull(ints); + assertTrue(Arrays.equals(new Integer[]{ + new Integer(123), new Integer(456) + }, ints)); + + Double[] doubles = (Double[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Double[].class); + assertNotNull(doubles); + assertTrue(Arrays.equals(new Double[]{new Double(123), new Double(456)}, doubles)); + + Float[] floats = (Float[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Float[].class); + assertNotNull(floats); + assertTrue(Arrays.equals(new Float[]{new Float(123), new Float(456)}, floats)); + + Boolean[] booleans = (Boolean[]) converter.convertValue(context, null, null, null, new String[]{ + "true", "false" + }, Boolean[].class); + assertNotNull(booleans); + assertTrue(Arrays.equals(new Boolean[]{Boolean.TRUE, Boolean.FALSE}, booleans)); + } + + public void testStringArrayToPrimitives() throws OgnlException { + long[] longs = (long[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, long[].class); + assertNotNull(longs); + assertTrue(Arrays.equals(new long[]{123, 456}, longs)); + + int[] ints = (int[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, int[].class); + assertNotNull(ints); + assertTrue(Arrays.equals(new int[]{123, 456}, ints)); + + double[] doubles = (double[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, double[].class); + assertNotNull(doubles); + assertTrue(Arrays.equals(new double[]{123, 456}, doubles)); + + float[] floats = (float[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, float[].class); + assertNotNull(floats); + assertTrue(Arrays.equals(new float[]{123, 456}, floats)); + + boolean[] booleans = (boolean[]) converter.convertValue(context, null, null, null, new String[]{ + "true", "false" + }, boolean[].class); + assertNotNull(booleans); + assertTrue(Arrays.equals(new boolean[]{true, false}, booleans)); + } + + public void testStringArrayToSet() { + Set list = new HashSet(); + list.add("foo"); + list.add("bar"); + list.add("baz"); + assertEquals(list, converter.convertValue(context, null, null, null, new String[]{ + "foo", "bar", "bar", "baz" + }, Set.class)); + } + + // TODO: Fixme... This test does not work with GenericsObjectDeterminer! + public void testStringToCollectionConversion() { + ValueStack stack = ActionContext.getContext().getValueStack(); + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + AnnotationUser user = new AnnotationUser(); + stack.push(user); + + stack.setValue("list", "asdf"); + assertNotNull(user.getList()); + assertEquals(1, user.getList().size()); + assertEquals(String.class, user.getList().get(0).getClass()); + assertEquals("asdf", user.getList().get(0)); + } + + public void testStringToCustomTypeUsingCustomConverter() { + // the converter needs to be registered as the Bar.class converter + // it won't be detected from the Foo-conversion.properties + // because the Foo-conversion.properties file is only used when converting a property of Foo + converter.registerConverter(Bar.class.getName(), new FooBarConverter()); + + Bar bar = (Bar) converter.convertValue(null, null, null, null, "blah:123", Bar.class); + assertNotNull("conversion failed", bar); + assertEquals(123, bar.getSomethingElse()); + assertEquals("blah", bar.getTitle()); + } + + public void testStringToPrimitiveWrappers() { + assertEquals(new Long(123), converter.convertValue(context, null, null, null, "123", Long.class)); + assertEquals(new Integer(123), converter.convertValue(context, null, null, null, "123", Integer.class)); + assertEquals(new Double(123.5), converter.convertValue(context, null, null, null, "123.5", Double.class)); + assertEquals(new Float(123.5), converter.convertValue(context, null, null, null, "123.5", float.class)); + assertEquals(new Boolean(false), converter.convertValue(context, null, null, null, "false", Boolean.class)); + assertEquals(new Boolean(true), converter.convertValue(context, null, null, null, "true", Boolean.class)); + } + + public void testStringToPrimitives() { + assertEquals(new Long(123), converter.convertValue(context, null, null, null, "123", long.class)); + assertEquals(new Integer(123), converter.convertValue(context, null, null, null, "123", int.class)); + assertEquals(new Double(123.5), converter.convertValue(context, null, null, null, "123.5", double.class)); + assertEquals(new Float(123.5), converter.convertValue(context, null, null, null, "123.5", float.class)); + assertEquals(new Boolean(false), converter.convertValue(context, null, null, null, "false", boolean.class)); + assertEquals(new Boolean(true), converter.convertValue(context, null, null, null, "true", boolean.class)); + assertEquals(new BigDecimal(123.5), converter.convertValue(context, null, null, null, "123.5", BigDecimal.class)); + assertEquals(new BigInteger("123"), converter.convertValue(context, null, null, null, "123", BigInteger.class)); + } + + public void testValueStackWithTypeParameter() { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(new Foo1()); + Bar1 bar = (Bar1) stack.findValue("bar", Bar1.class); + assertNotNull(bar); + } + + public void testGenericProperties() { + GenericsBean gb = new GenericsBean(); + ValueStack stack = ac.getValueStack(); + stack.push(gb); + + String[] value = new String[] {"123.12", "123.45"}; + stack.setValue("doubles", value); + assertEquals(2, gb.getDoubles().size()); + assertEquals(Double.class, gb.getDoubles().get(0).getClass()); + assertEquals(new Double(123.12), gb.getDoubles().get(0)); + assertEquals(new Double(123.45), gb.getDoubles().get(1)); + } + + public void testGenericPropertiesFromField() { + GenericsBean gb = new GenericsBean(); + ValueStack stack = ac.getValueStack(); + stack.push(gb); + + stack.setValue("genericMap[123.12]", "66"); + stack.setValue("genericMap[456.12]", "42"); + + assertEquals(2, gb.getGenericMap().size()); + assertEquals(Integer.class, stack.findValue("genericMap.get(123.12).class")); + assertEquals(Integer.class, stack.findValue("genericMap.get(456.12).class")); + assertEquals(66, stack.findValue("genericMap.get(123.12)")); + assertEquals(42, stack.findValue("genericMap.get(456.12)")); + assertEquals(true, stack.findValue("genericMap.containsValue(66)")); + assertEquals(true, stack.findValue("genericMap.containsValue(42)")); + assertEquals(true, stack.findValue("genericMap.containsKey(123.12)")); + assertEquals(true, stack.findValue("genericMap.containsKey(456.12)")); + } + + public void testGenericPropertiesFromSetter() { + GenericsBean gb = new GenericsBean(); + ValueStack stack = ac.getValueStack(); + stack.push(gb); + + stack.setValue("genericMap[123.12]", "66"); + stack.setValue("genericMap[456.12]", "42"); + + assertEquals(2, gb.getGenericMap().size()); + assertEquals(Integer.class, stack.findValue("genericMap.get(123.12).class")); + assertEquals(Integer.class, stack.findValue("genericMap.get(456.12).class")); + assertEquals(66, stack.findValue("genericMap.get(123.12)")); + assertEquals(42, stack.findValue("genericMap.get(456.12)")); + assertEquals(true, stack.findValue("genericMap.containsValue(66)")); + assertEquals(true, stack.findValue("genericMap.containsValue(42)")); + assertEquals(true, stack.findValue("genericMap.containsKey(123.12)")); + assertEquals(true, stack.findValue("genericMap.containsKey(456.12)")); + } + + public void testGenericPropertiesFromGetter() { + GenericsBean gb = new GenericsBean(); + ValueStack stack = ac.getValueStack(); + stack.push(gb); + + assertEquals(1, gb.getGetterList().size()); + assertEquals(Double.class, stack.findValue("getterList.get(0).class")); + assertEquals(new Double(42.42), stack.findValue("getterList.get(0)")); + assertEquals(new Double(42.42), gb.getGetterList().get(0)); + + } + + + // FIXME: Implement nested Generics such as: List of Generics List, Map of Generic keys/values, etc... + public void no_testGenericPropertiesWithNestedGenerics() { + GenericsBean gb = new GenericsBean(); + ValueStack stack = ac.getValueStack(); + stack.push(gb); + + stack.setValue("extendedMap[123.12]", new String[] {"1", "2", "3", "4"}); + stack.setValue("extendedMap[456.12]", new String[] {"5", "6", "7", "8", "9"}); + + System.out.println("gb.getExtendedMap(): " + gb.getExtendedMap()); + + assertEquals(2, gb.getExtendedMap().size()); + System.out.println(stack.findValue("extendedMap")); + assertEquals(4, stack.findValue("extendedMap.get(123.12).size")); + assertEquals(5, stack.findValue("extendedMap.get(456.12).size")); + + assertEquals("1", stack.findValue("extendedMap.get(123.12).get(0)")); + assertEquals("5", stack.findValue("extendedMap.get(456.12).get(0)")); + assertEquals(Integer.class, stack.findValue("extendedMap.get(123.12).get(0).class")); + assertEquals(Integer.class, stack.findValue("extendedMap.get(456.12).get(0).class")); + + assertEquals(List.class, stack.findValue("extendedMap.get(123.12).class")); + assertEquals(List.class, stack.findValue("extendedMap.get(456.12).class")); + + } + + public static class Foo1 { + public Bar1 getBar() { + return new Bar1Impl(); + } + } + + public interface Bar1 { + } + + public static class Bar1Impl implements Bar1 { + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + converter = container.getInstance(XWorkConverter.class); + + ac = ActionContext.getContext(); + ac.setLocale(Locale.US); + context = ac.getContextMap(); + } + + @Override + protected void tearDown() throws Exception { + ActionContext.setContext(null); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/FooBarConverter.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/FooBarConverter.java new file mode 100644 index 000000000..6d7ec50e5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/FooBarConverter.java @@ -0,0 +1,72 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.util.AnnotatedCat; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.util.Cat; + +import java.lang.reflect.Member; +import java.util.Map; + + +/** + * @author Pat Lightbody + * @author $Author$ + * @version $Revision$ + */ +public class FooBarConverter extends DefaultTypeConverter { + + @Override + public Object convertValue(Map context, Object value, Class toType) { + if (toType == String.class) { + Bar bar = (Bar) value; + + return bar.getTitle() + ":" + bar.getSomethingElse(); + } else if (toType == Bar.class) { + String valueStr = (String) value; + int loc = valueStr.indexOf(":"); + String title = valueStr.substring(0, loc); + String rest = valueStr.substring(loc + 1); + + Bar bar = new Bar(); + bar.setTitle(title); + bar.setSomethingElse(Integer.parseInt(rest)); + + return bar; + } else if (toType == Cat.class) { + Cat cat = new Cat(); + cat.setName((String) value); + + return cat; + } else if (toType == AnnotatedCat.class) { + AnnotatedCat cat = new AnnotatedCat(); + cat.setName((String) value); + + return cat; + } else { + System.out.println("Don't know how to convert between " + value.getClass().getName() + + " and " + toType.getName()); + } + + return null; + } + + @Override + public Object convertValue(Map context, Object source, Member member, String property, Object value, Class toClass) { + return convertValue(context, value, toClass); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/FooNumberConverter.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/FooNumberConverter.java new file mode 100644 index 000000000..86ba9e957 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/FooNumberConverter.java @@ -0,0 +1,18 @@ +package com.opensymphony.xwork2.conversion.impl; + +import java.util.Map; + +public class FooNumberConverter extends DefaultTypeConverter { + @Override + public Object convertValue(Map map, Object object, Class aClass) { + String s = (String) object; + + int length = s.length(); + StringBuilder r = new StringBuilder(); + for (int i = length; i > 0; i--) { + r.append(s.charAt(i - 1)); + } + + return super.convertValue(map, r.toString(), aClass); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandlerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandlerTest.java new file mode 100644 index 000000000..ac9271966 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandlerTest.java @@ -0,0 +1,54 @@ +/* + * 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.conversion.impl; + +import junit.framework.TestCase; + + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class InstantiatingNullHandlerTest extends TestCase { + + public void testBlank() { + + } + /*public void testInheritance() { + Tiger t = new Tiger(); + CompoundRoot root = new CompoundRoot(); + root.add(t); + + Map context = new OgnlContext(); + context.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.TRUE); + + InstantiatingNullHandler nh = new InstantiatingNullHandler(); + + Object dogList = nh.nullPropertyValue(context, root, "dogs"); + Class clazz = nh.getCollectionType(Tiger.class, "dogs"); + assertEquals(Dog.class, clazz); + assertNotNull(dogList); + assertTrue(dogList instanceof List); + + Object kittenList = nh.nullPropertyValue(context, root, "kittens"); + clazz = nh.getCollectionType(Tiger.class, "kittens"); + assertEquals(Cat.class, clazz); + assertNotNull(kittenList); + assertTrue(kittenList instanceof List); + }*/ +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/ParentClass.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/ParentClass.java new file mode 100644 index 000000000..a8fc59e1a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/ParentClass.java @@ -0,0 +1,27 @@ +package com.opensymphony.xwork2.conversion.impl; + +/** + * ParentClass + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class ParentClass { + + public enum NestedEnum { + TEST, + TEST2, + TEST3 + } + + + private NestedEnum value; + + public void setValue(NestedEnum value) { + this.value = value; + } + + public NestedEnum getValue() { + return value; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java new file mode 100644 index 000000000..77e9884e0 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java @@ -0,0 +1,261 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.test.annotations.Person; +import junit.framework.TestCase; + +import java.text.DateFormat; +import java.util.*; +import java.lang.reflect.Member; + +/** + * Test case for XWorkBasicConverter + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class XWorkBasicConverterTest extends TestCase { + + // TODO: test for every possible conversion + // take into account of empty string + // primitive -> conversion error when empty string is passed + // object -> return null when empty string is passed + + public void testDateConversionWithEmptyValue() { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue(new HashMap(), null, null, null, "", Date.class); + // we must not get XWorkException as that will caused a conversion error + assertNull(convertedObject); + } + + public void testDateConversionWithInvalidValue() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + try { + Object convertedObject = basicConverter.convertValue(new HashMap(), null, null, null, "asdsd", Date.class); + fail("XWorkException expected - conversion error occurred"); + } catch (XWorkException e) { + // we MUST get this exception as this is a conversion error + } + } + + public void testDateWithLocalePoland() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + + Map map = new HashMap(); + Locale locale = new Locale("pl", "PL"); + map.put(ActionContext.LOCALE, locale); + + String reference = "2009-01-09"; + Object convertedObject = basicConverter.convertValue(map, null, null, null, reference, Date.class); + + assertNotNull(convertedObject); + + compareDates(locale, convertedObject); + } + + public void testDateWithLocaleFrance() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + + Map map = new HashMap(); + Locale locale = new Locale("fr", "FR"); + map.put(ActionContext.LOCALE, locale); + + String reference = "09/01/2009"; + Object convertedObject = basicConverter.convertValue(map, null, null, null, reference, Date.class); + + assertNotNull(convertedObject); + + compareDates(locale, convertedObject); + } + + public void testDateWithLocaleUK() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + + Map map = new HashMap(); + Locale locale = new Locale("en", "US"); + map.put(ActionContext.LOCALE, locale); + + String reference = "01/09/2009"; + Object convertedObject = basicConverter.convertValue(map, null, null, null, reference, Date.class); + + assertNotNull(convertedObject); + + compareDates(locale, convertedObject); + } + + private void compareDates(Locale locale, Object convertedObject) { + Calendar cal = Calendar.getInstance(locale); + cal.set(Calendar.YEAR, 2009); + cal.set(Calendar.MONTH, Calendar.JANUARY); + cal.set(Calendar.DATE, 9); + + Calendar cal1 = Calendar.getInstance(locale); + cal1.setTime((Date) convertedObject); + + assertEquals(cal.get(Calendar.YEAR), cal1.get(Calendar.YEAR)); + assertEquals(cal.get(Calendar.MONTH), cal1.get(Calendar.MONTH)); + assertEquals(cal.get(Calendar.DATE), cal1.get(Calendar.DATE)); + + DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); + assertEquals(df.format(cal.getTime()), df.format(convertedObject)); + } + + public void testEmptyArrayConversion() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue(new HashMap(), null, null, null, new Object[]{}, Object[].class); + // we must not get XWorkException as that will caused a conversion error + assertEquals(Object[].class, convertedObject.getClass()); + Object[] obj = (Object[]) convertedObject; + assertEquals(0, obj.length); + } + + public void testNullArrayConversion() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue(new HashMap(), null, null, null, null, Object[].class); + // we must not get XWorkException as that will caused a conversion error + assertNull(convertedObject); + } + + /* the code below has been disabled as it causes sideffects in Strtus2 (XW-512) + public void testXW490ConvertStringToDouble() throws Exception { + Locale locale = new Locale("DA"); // let's use a not common locale such as Denmark + + Map ctx = new HashMap(); + ctx.put(ActionContext.LOCALE, locale); + + XWorkBasicConverter conv = new XWorkBasicConverter(); + // decimal seperator is , in Denmark so we should write 123,99 as input + Double value = (Double) conv.convertValue(ctx, null, null, null, "123,99", Double.class); + assertNotNull(value); + + // output is as expected a real double value converted using Denmark as locale + assertEquals(123.99d, value.doubleValue(), 0.001d); + } + + public void testXW49ConvertDoubleToString() throws Exception { + Locale locale = new Locale("DA"); // let's use a not common locale such as Denmark + + Map ctx = new HashMap(); + ctx.put(ActionContext.LOCALE, locale); + + XWorkBasicConverter conv = new XWorkBasicConverter(); + // decimal seperator is , in Denmark so we should write 123,99 as input + String value = (String) conv.convertValue(ctx, null, null, null, new Double("123.99"), String.class); + assertNotNull(value); + + // output should be formatted according to Danish locale using , as decimal seperator + assertEquals("123,99", value); + } + */ + + public void testDoubleValues() { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + + assertTrue(basicConverter.isInRange(-1.2, "-1.2", Double.class)); + assertTrue(basicConverter.isInRange(1.5, "1.5", Double.class)); + + Object value = basicConverter.convertValue("-1.3", double.class); + assertNotNull(value); + assertEquals(-1.3, value); + + value = basicConverter.convertValue("1.8", double.class); + assertNotNull(value); + assertEquals(1.8, value); + + value = basicConverter.convertValue("-1.9", double.class); + assertNotNull(value); + assertEquals(-1.9, value); + + value = basicConverter.convertValue("1.7", Double.class); + assertNotNull(value); + assertEquals(1.7, value); + + value = basicConverter.convertValue("0.0", Double.class); + assertNotNull(value); + assertEquals(0.0, value); + + value = basicConverter.convertValue("0.0", double.class); + assertNotNull(value); + assertEquals(0.0, value); + } + + public void testFloatValues() { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + + assertTrue(basicConverter.isInRange(-1.65, "-1.65", Float.class)); + assertTrue(basicConverter.isInRange(1.9876, "1.9876", float.class)); + + Float value = (Float) basicConverter.convertValue("-1.444401", Float.class); + assertNotNull(value); + assertEquals(Float.valueOf("-1.444401"), value); + + value = (Float) basicConverter.convertValue("1.46464989", Float.class); + assertNotNull(value); + assertEquals(Float.valueOf(1.46464989f), value); + } + + public void testNegativeFloatValue() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue("-94.1231233", Float.class); + assertTrue(convertedObject instanceof Float); + assertEquals(-94.1231233f, ((Float) convertedObject).floatValue(), 0.0001); + } + + public void testPositiveFloatValue() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue("94.1231233", Float.class); + assertTrue(convertedObject instanceof Float); + assertEquals(94.1231233f, ((Float) convertedObject).floatValue(), 0.0001); + } + + + public void testNegativeDoubleValue() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue("-94.1231233", Double.class); + assertTrue(convertedObject instanceof Double); + assertEquals(-94.1231233d, ((Double) convertedObject).doubleValue(), 0.0001); + } + + public void testPositiveDoubleValue() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue("94.1231233", Double.class); + assertTrue(convertedObject instanceof Double); + assertEquals(94.1231233d, ((Double) convertedObject).doubleValue(), 0.0001); + } + + public void testNestedEnumValue() throws Exception { + XWorkBasicConverter basicConverter = new XWorkBasicConverter(); + Object convertedObject = basicConverter.convertValue(ParentClass.NestedEnum.TEST.name(), ParentClass.NestedEnum.class); + assertTrue(convertedObject instanceof ParentClass.NestedEnum); + assertEquals(ParentClass.NestedEnum.TEST, convertedObject); + } + + + public void testConvert() { + XWorkBasicConverter converter = new XWorkBasicConverter(); + Map context = new HashMap(); + Person o = new Person(); + Member member = null; + String s = "names"; + Object value = new Person[0]; + Class toType = String.class; + converter.convertValue(context, value, member, s, value, toType); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java new file mode 100644 index 000000000..ba2fdddcb --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java @@ -0,0 +1,692 @@ +/* + * 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.conversion.impl; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.test.ModelDrivenAction2; +import com.opensymphony.xwork2.test.User; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.util.Cat; +import com.opensymphony.xwork2.util.Foo; +import com.opensymphony.xwork2.util.FurColor; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.OgnlException; +import ognl.OgnlRuntime; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + + +/** + * @author $Author$ + * @version $Revision$ + */ +public class XWorkConverterTest extends XWorkTestCase { + + Map context; + XWorkConverter converter; + OgnlValueStack stack; + +// public void testConversionToSetKeepsOriginalSetAndReplacesContents() { +// ValueStack stack = ValueStackFactory.getFactory().createValueStack(); +// +// Map stackContext = stack.getContext(); +// stackContext.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.TRUE); +// stackContext.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.TRUE); +// stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); +// +// String[] param = new String[] {"abc", "def", "ghi"}; +// List paramList = Arrays.asList(param); +// +// List originalList = new ArrayList(); +// originalList.add("jkl"); +// originalList.add("mno"); +// +// User user = new User(); +// user.setList(originalList); +// stack.push(user); +// +// stack.setValue("list", param); +// +// List userList = user.getList(); +// assertEquals(3,userList.size()); +// assertEquals(paramList,userList); +// assertSame(originalList,userList); +// } + + public void testArrayToNumberConversion() { + String[] value = new String[]{"12345"}; + assertEquals(new Integer(12345), converter.convertValue(context, null, null, null, value, Integer.class)); + assertEquals(new Long(12345), converter.convertValue(context, null, null, null, value, Long.class)); + value[0] = "123.45"; + assertEquals(new Float(123.45), converter.convertValue(context, null, null, null, value, Float.class)); + assertEquals(new Double(123.45), converter.convertValue(context, null, null, null, value, Double.class)); + value[0] = "1234567890123456789012345678901234567890"; + assertEquals(new BigInteger(value[0]), converter.convertValue(context, null, null, null, value, BigInteger.class)); + value[0] = "1234567890123456789.012345678901234567890"; + assertEquals(new BigDecimal(value[0]), converter.convertValue(context, null, null, null, value, BigDecimal.class)); + } + + public void testDateConversion() throws ParseException { + java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis()); + assertEquals(sqlDate, converter.convertValue(context, null, null, null, sqlDate, Date.class)); + + SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss"); + Date date = format.parse("01/10/2001 00:00:00"); + + SimpleDateFormat formatt = new SimpleDateFormat("hh:mm:ss"); + java.sql.Time datet = new java.sql.Time(formatt.parse("10:11:12").getTime()); + + String dateStr = (String) converter.convertValue(context, null, null, null, date, String.class); + String datetStr = (String) converter.convertValue(context, null, null, null, datet, String.class); + + Date date2 = (Date) converter.convertValue(context, null, null, null, dateStr, Date.class); + assertEquals(date, date2); + java.sql.Date date3 = (java.sql.Date) converter.convertValue(context, null, null, null, dateStr, java.sql.Date.class); + assertEquals(date, date3); + java.sql.Timestamp ts = (java.sql.Timestamp) converter.convertValue(context, null, null, null, dateStr, java.sql.Timestamp.class); + assertEquals(date, ts); + java.sql.Time time1 = (java.sql.Time) converter.convertValue(context, null, null, null, datetStr, java.sql.Time.class); + assertEquals(datet, time1); + } + + public void testFieldErrorMessageAddedForComplexProperty() { + SimpleAction action = new SimpleAction(); + action.setBean(new TestBean()); + + stack.push(action); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + ognlStackContext.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, "bean.birth"); + + String[] value = new String[]{"invalid date"}; + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action.getBean(), null, "birth", value, Date.class)); + stack.pop(); + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertTrue(conversionErrors.size() == 1); + assertEquals(value, conversionErrors.get("bean.birth")); + } + + public void testFieldErrorMessageAddedWhenConversionFails() { + SimpleAction action = new SimpleAction(); + action.setDate(null); + + stack.push(action); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + String[] value = new String[]{"invalid date"}; + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action, null, "date", value, Date.class)); + stack.pop(); + + Map conversionErrors = (Map) ognlStackContext.get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertEquals(1, conversionErrors.size()); + assertNotNull(conversionErrors.get("date")); + assertEquals(value, conversionErrors.get("date")); + } + + public void testFieldErrorMessageAddedWhenConversionFailsOnModelDriven() { + ModelDrivenAction action = new ModelDrivenAction(); + stack.push(action); + stack.push(action.getModel()); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + String[] value = new String[]{"invalid date"}; + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action, null, "birth", value, Date.class)); + stack.pop(); + stack.pop(); + + Map conversionErrors = (Map) ognlStackContext.get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertEquals(1, conversionErrors.size()); + assertNotNull(conversionErrors.get("birth")); + assertEquals(value, conversionErrors.get("birth")); + } + + public void testDateStrictConversion() throws Exception { + // see XW-341 + String dateStr = "13/01/2005"; // us date format is used in context + Object res = converter.convertValue(context, null, null, null, dateStr, Date.class); + assertEquals(res, OgnlRuntime.NoConversionPossible); + + dateStr = "02/30/2005"; // us date format is used in context + res = converter.convertValue(context, null, null, null, dateStr, Date.class); + assertEquals(res, OgnlRuntime.NoConversionPossible); + + // and test a date that is passable + SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy"); + dateStr = "12/31/2005"; // us date format + res = converter.convertValue(context, null, null, null, dateStr, Date.class); + Date date = format.parse(dateStr); + assertNotSame(res, OgnlRuntime.NoConversionPossible); + assertEquals(date, res); + } + + + public void testFindConversionErrorMessage() { + ModelDrivenAction action = new ModelDrivenAction(); + stack.push(action); + stack.push(action.getModel()); + + String message = XWorkConverter.getConversionErrorMessage("birth", stack); + assertNotNull(message); + assertEquals("Invalid date for birth.", message); + + message = XWorkConverter.getConversionErrorMessage("foo", stack); + assertNotNull(message); + assertEquals("Invalid field value for field \"foo\".", message); + } + + public void testFindConversionMappingForInterface() { + ModelDrivenAction2 action = new ModelDrivenAction2(); + stack.push(action); + stack.push(action.getModel()); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + String value = "asdf:123"; + Object o = converter.convertValue(ognlStackContext, action.getModel(), null, "barObj", value, Bar.class); + assertNotNull(o); + assertTrue(o instanceof Bar); + + Bar b = (Bar) o; + assertEquals(value, b.getTitle() + ":" + b.getSomethingElse()); + } + + public void testLocalizedDateConversion() throws Exception { + Date date = new Date(System.currentTimeMillis()); + Locale locale = Locale.GERMANY; + DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); + String dateString = df.format(date); + context.put(ActionContext.LOCALE, locale); + assertEquals(dateString, converter.convertValue(context, null, null, null, date, String.class)); + } + + public void testStringToIntConversions() { + SimpleAction action = new SimpleAction(); + action.setBean(new TestBean()); + + stack.push(action); + + Map ognlStackContext = stack.getContext(); + ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action.getBean(), null, "count", "111.1", int.class)); + stack.pop(); + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertNotNull(conversionErrors); + assertTrue(conversionErrors.size() == 1); + } + + public void testStringArrayToCollection() { + List list = new ArrayList(); + list.add("foo"); + list.add("bar"); + list.add("baz"); + assertEquals(list, converter.convertValue(context, null, null, null, new String[]{ + "foo", "bar", "baz" + }, Collection.class)); + } + + public void testStringArrayToList() { + List list = new ArrayList(); + list.add("foo"); + list.add("bar"); + list.add("baz"); + assertEquals(list, converter.convertValue(context, null, null, null, new String[]{ + "foo", "bar", "baz" + }, List.class)); + } + + public void testStringArrayToPrimitiveWrappers() { + Long[] longs = (Long[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Long[].class); + assertNotNull(longs); + assertTrue(Arrays.equals(new Long[]{new Long(123), new Long(456)}, longs)); + + Integer[] ints = (Integer[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Integer[].class); + assertNotNull(ints); + assertTrue(Arrays.equals(new Integer[]{ + new Integer(123), new Integer(456) + }, ints)); + + Double[] doubles = (Double[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Double[].class); + assertNotNull(doubles); + assertTrue(Arrays.equals(new Double[]{new Double(123), new Double(456)}, doubles)); + + Float[] floats = (Float[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, Float[].class); + assertNotNull(floats); + assertTrue(Arrays.equals(new Float[]{new Float(123), new Float(456)}, floats)); + + Boolean[] booleans = (Boolean[]) converter.convertValue(context, null, null, null, new String[]{ + "true", "false" + }, Boolean[].class); + assertNotNull(booleans); + assertTrue(Arrays.equals(new Boolean[]{Boolean.TRUE, Boolean.FALSE}, booleans)); + } + + public void testStringArrayToPrimitives() throws OgnlException { + long[] longs = (long[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, long[].class); + assertNotNull(longs); + assertTrue(Arrays.equals(new long[]{123, 456}, longs)); + + int[] ints = (int[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, int[].class); + assertNotNull(ints); + assertTrue(Arrays.equals(new int[]{123, 456}, ints)); + + double[] doubles = (double[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, double[].class); + assertNotNull(doubles); + assertTrue(Arrays.equals(new double[]{123, 456}, doubles)); + + float[] floats = (float[]) converter.convertValue(context, null, null, null, new String[]{ + "123", "456" + }, float[].class); + assertNotNull(floats); + assertTrue(Arrays.equals(new float[]{123, 456}, floats)); + + boolean[] booleans = (boolean[]) converter.convertValue(context, null, null, null, new String[]{ + "true", "false" + }, boolean[].class); + assertNotNull(booleans); + assertTrue(Arrays.equals(new boolean[]{true, false}, booleans)); + } + + public void testStringArrayToSet() { + Set list = new HashSet(); + list.add("foo"); + list.add("bar"); + list.add("baz"); + assertEquals(list, converter.convertValue(context, null, null, null, new String[]{ + "foo", "bar", "bar", "baz" + }, Set.class)); + } + + public void testStringToCollectionConversion() { + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + User user = new User(); + stack.push(user); + + stack.setValue("list", "asdf"); + assertNotNull(user.getList()); + assertEquals(1, user.getList().size()); + assertEquals(String.class, user.getList().get(0).getClass()); + assertEquals("asdf", user.getList().get(0)); + } + + public void testStringToCustomTypeUsingCustomConverter() { + // the converter needs to be registered as the Bar.class converter + // it won't be detected from the Foo-conversion.properties + // because the Foo-conversion.properties file is only used when converting a property of Foo + converter.registerConverter(Bar.class.getName(), new FooBarConverter()); + + Bar bar = (Bar) converter.convertValue(null, null, null, null, "blah:123", Bar.class); + assertNotNull("conversion failed", bar); + assertEquals(123, bar.getSomethingElse()); + assertEquals("blah", bar.getTitle()); + } + + public void testStringToCustomTypeUsingCustomConverterFromProperties() throws Exception { + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(new ClassLoader(cl) { + @Override + public Enumeration getResources(String name) throws IOException { + if ("xwork-conversion.properties".equals(name)) { + return new Enumeration() { + boolean done = false; + public boolean hasMoreElements() { + return !done; + } + + public URL nextElement() { + if (done) { + throw new RuntimeException("Conversion configuration loading " + + "failed because it asked the enumeration for the next URL " + + "too many times"); + } + + done = true; + return getClass().getResource("/com/opensymphony/xwork2/conversion/impl/test-xwork-conversion.properties"); + } + }; + } else { + return super.getResources(name); + } + } + }); + setUp(); + } finally { + Thread.currentThread().setContextClassLoader(cl); + } + Bar bar = (Bar) converter.convertValue(null, null, null, null, "blah:123", Bar.class); + assertNotNull("conversion failed", bar); + assertEquals(123, bar.getSomethingElse()); + assertEquals("blah", bar.getTitle()); + } + + public void testStringToPrimitiveWrappers() { + assertEquals(new Long(123), converter.convertValue(context, null, null, null, "123", Long.class)); + assertEquals(new Integer(123), converter.convertValue(context, null, null, null, "123", Integer.class)); + assertEquals(new Double(123.5), converter.convertValue(context, null, null, null, "123.5", Double.class)); + assertEquals(new Float(123.5), converter.convertValue(context, null, null, null, "123.5", float.class)); + assertEquals(new Boolean(false), converter.convertValue(context, null, null, null, "false", Boolean.class)); + assertEquals(new Boolean(true), converter.convertValue(context, null, null, null, "true", Boolean.class)); + } + + public void testStringToPrimitives() { + assertEquals(new Long(123), converter.convertValue(context, null, null, null, "123", long.class)); + assertEquals(new Double(123.5), converter.convertValue(context, null, null, null, "123.5", double.class)); + assertEquals(new Float(123.5), converter.convertValue(context, null, null, null, "123.5", float.class)); + assertEquals(new Boolean(false), converter.convertValue(context, null, null, null, "false", boolean.class)); + assertEquals(new Boolean(true), converter.convertValue(context, null, null, null, "true", boolean.class)); + assertEquals(new BigDecimal(123.5), converter.convertValue(context, null, null, null, "123.5", BigDecimal.class)); + assertEquals(new BigInteger("123"), converter.convertValue(context, null, null, null, "123", BigInteger.class)); + } + + public void testOverflows() { + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Double.MAX_VALUE + "1", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Double.MIN_VALUE + "-1", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Double.MAX_VALUE + "1", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Double.MIN_VALUE + "-1", Double.class)); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Float.MAX_VALUE + "1", float.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Float.MIN_VALUE + "-1", float.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Float.MAX_VALUE + "1", Float.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Float.MIN_VALUE + "-1", Float.class)); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Integer.MAX_VALUE + "1", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Integer.MIN_VALUE + "-1", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Integer.MAX_VALUE + "1", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Integer.MIN_VALUE + "-1", Integer.class)); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Byte.MAX_VALUE + "1", byte.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Byte.MIN_VALUE + "-1", byte.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Byte.MAX_VALUE + "1", Byte.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Byte.MIN_VALUE + "-1", Byte.class)); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Short.MAX_VALUE + "1", short.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Short.MIN_VALUE + "-1", short.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Short.MAX_VALUE + "1", Short.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Short.MIN_VALUE + "-1", Short.class)); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Long.MAX_VALUE + "1", long.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Long.MIN_VALUE + "-1", long.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Long.MAX_VALUE + "1", Long.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, Long.MIN_VALUE + "-1", Long.class)); + } + + public void testStringToInt() { + assertEquals(new Integer(123), converter.convertValue(context, null, null, null, "123", int.class)); + context.put(ActionContext.LOCALE, Locale.US); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123.12", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,23", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", int.class)); + context.put(ActionContext.LOCALE, Locale.GERMANY); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123.12", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,23", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234", int.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", int.class)); + } + + + public void testStringToInteger() { + assertEquals(new Integer(123), converter.convertValue(context, null, null, null, "123", Integer.class)); + context.put(ActionContext.LOCALE, Locale.US); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123.12", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", Integer.class)); + assertEquals(new Integer(1234), converter.convertValue(context, null, null, null, "1,234", Integer.class)); + // WRONG: locale separator is wrongly placed + assertEquals(new Integer(123), converter.convertValue(context, null, null, null, "1,23", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", Integer.class)); + + context.put(ActionContext.LOCALE, Locale.GERMANY); + // WRONG: locale separator is wrongly placed + assertEquals(new Integer(12312), converter.convertValue(context, null, null, null, "123.12", Integer.class)); + assertEquals(new Integer(1234), converter.convertValue(context, null, null, null, "1.234", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,23", Integer.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", Integer.class)); + } + + public void testStringToPrimitiveDouble() { + assertEquals(new Double(123), converter.convertValue(context, null, null, null, "123", double.class)); + context.put(ActionContext.LOCALE, Locale.US); + assertEquals(new Double(123.12), converter.convertValue(context, null, null, null, "123.12", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,23", double.class)); + assertEquals(new Double(1.234), converter.convertValue(context, null, null, null, "1.234", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", double.class)); + + context.put(ActionContext.LOCALE, Locale.GERMANY); + assertEquals(new Double(123.12), converter.convertValue(context, null, null, null, "123.12", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,23", double.class)); + assertEquals(new Double(1.234), converter.convertValue(context, null, null, null, "1.234", double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", double.class)); + } + + public void testStringToDouble() { + assertEquals(new Double(123), converter.convertValue(context, null, null, null, "123", Double.class)); + context.put(ActionContext.LOCALE, Locale.US); + assertEquals(new Double(123.12), converter.convertValue(context, null, null, null, "123.12", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", Double.class)); + assertEquals(new Double(1234), converter.convertValue(context, null, null, null, "1,234", Double.class)); + assertEquals(new Double(1234.12), converter.convertValue(context, null, null, null, "1,234.12", Double.class)); + // WRONG: locale separator is wrongly placed + assertEquals(new Double(123), converter.convertValue(context, null, null, null, "1,23", Double.class)); + assertEquals(new Double(1.234), converter.convertValue(context, null, null, null, "1.234", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1.234,12", Double.class)); + + context.put(ActionContext.LOCALE, Locale.GERMANY); + // WRONG: locale separator is wrongly placed + assertEquals(new Double(12312), converter.convertValue(context, null, null, null, "123.12", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "123aa", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "aa123", Double.class)); + assertEquals(new Double(1.234), converter.convertValue(context, null, null, null, "1,234", Double.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "1,234.12", Double.class)); + assertEquals(new Double(1.23), converter.convertValue(context, null, null, null, "1,23", Double.class)); + assertEquals(new Double(1234), converter.convertValue(context, null, null, null, "1.234", Double.class)); + assertEquals(new Double(1234.12), converter.convertValue(context, null, null, null, "1.234,12", Double.class)); + + } + + public void testStringToEnum() { + assertEquals(FurColor.BLACK, converter.convertValue(context, null, null, null, "BLACK", FurColor.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "black", FurColor.class)); + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, null, null, null, "red", FurColor.class)); + } + + // Testing for null result on non-primitive Number types supplied as empty String or + public void testNotPrimitiveDefaultsToNull() { + assertEquals(null, converter.convertValue(context, null, null, null, null, Double.class)); + assertEquals(null, converter.convertValue(context, null, null, null, "", Double.class)); + + assertEquals(null, converter.convertValue(context, null, null, null, null, Integer.class)); + assertEquals(null, converter.convertValue(context, null, null, null, "", Integer.class)); + + assertEquals(null, converter.convertValue(context, null, null, null, null, Float.class)); + assertEquals(null, converter.convertValue(context, null, null, null, "", Float.class)); + + assertEquals(null, converter.convertValue(context, null, null, null, null, Character.class)); + assertEquals(null, converter.convertValue(context, null, null, null, "", Character.class)); + + assertEquals(null, converter.convertValue(context, null, null, null, null, Long.class)); + assertEquals(null, converter.convertValue(context, null, null, null, "", Long.class)); + + assertEquals(null, converter.convertValue(context, null, null, null, null, Short.class)); + assertEquals(null, converter.convertValue(context, null, null, null, "", Short.class)); + + } + + public void testConvertChar() { + assertEquals(new Character('A'), converter.convertValue(context, "A", char.class)); + assertEquals(new Character('Z'), converter.convertValue(context, "Z", char.class)); + assertEquals(new Character('A'), converter.convertValue(context, "A", Character.class)); + assertEquals(new Character('Z'), converter.convertValue(context, "Z", Character.class)); + + assertEquals(new Character('A'), converter.convertValue(context, new Character('A'), char.class)); + assertEquals(new Character('Z'), converter.convertValue(context, new Character('Z'), char.class)); + assertEquals(new Character('A'), converter.convertValue(context, new Character('A'), Character.class)); + assertEquals(new Character('Z'), converter.convertValue(context, new Character('Z'), Character.class)); + + assertEquals(new Character('D'), converter.convertValue(context, "DEF", char.class)); + assertEquals(new Character('X'), converter.convertValue(context, "XYZ", Character.class)); + assertEquals(new Character(' '), converter.convertValue(context, " ", Character.class)); + assertEquals(new Character(' '), converter.convertValue(context, " ", char.class)); + + assertEquals(null, converter.convertValue(context, "", char.class)); + } + + public void testConvertClass() { + Class clazz = (Class) converter.convertValue(context, "java.util.Date", Class.class); + assertEquals(Date.class.getName(), clazz.getName()); + + Class clazz2 = (Class) converter.convertValue(context, "com.opensymphony.xwork2.util.Bar", Class.class); + assertEquals(Bar.class.getName(), clazz2.getName()); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, "com.opensymphony.xwork2.util.IDoNotExist", Class.class)); + + assertEquals(OgnlRuntime.NoConversionPossible, converter.convertValue(context, new Bar(), Class.class)); // only supports string values + } + + public void testConvertBoolean() { + assertEquals(Boolean.TRUE, converter.convertValue(context, "true", Boolean.class)); + assertEquals(Boolean.FALSE, converter.convertValue(context, "false", Boolean.class)); + + assertEquals(Boolean.TRUE, converter.convertValue(context, Boolean.TRUE, Boolean.class)); + assertEquals(Boolean.FALSE, converter.convertValue(context, Boolean.FALSE, Boolean.class)); + + assertEquals(null, converter.convertValue(context, null, Boolean.class)); + assertEquals(Boolean.TRUE, converter.convertValue(context, new Bar(), Boolean.class)); // Ognl converter will default to true + } + + public void testConvertPrimitiveArraysToString() { + assertEquals("2, 3, 1", converter.convertValue(context, new int[]{2, 3, 1}, String.class)); + assertEquals("100, 200, 300", converter.convertValue(context, new long[]{100, 200, 300}, String.class)); + assertEquals("1.5, 2.5, 3.5", converter.convertValue(context, new double[]{1.5, 2.5, 3.5}, String.class)); + assertEquals("true, false, true", converter.convertValue(context, new boolean[]{true, false, true}, String.class)); + } + + public void testConvertSameCollectionToCollection() { + Collection names = new ArrayList(); + names.add("XWork"); + names.add("Struts"); + + Collection col = (Collection) converter.convertValue(context, names, Collection.class); + assertSame(names, col); + } + + public void testConvertSqlTimestamp() { + assertNotNull(converter.convertValue(context, new Timestamp(new Date().getTime()), String.class)); + assertNotNull(converter.convertValue(null, new Timestamp(new Date().getTime()), String.class)); + } + + public void testValueStackWithTypeParameter() { + stack.push(new Foo1()); + Bar1 bar = (Bar1) stack.findValue("bar", Bar1.class); + assertNotNull(bar); + } + + public void testNestedConverters() { + Cat cat = new Cat(); + cat.setFoo(new Foo()); + stack.push(cat); + stack.setValue("foo.number", "123"); + assertEquals(321, cat.getFoo().getNumber()); + } + + public static class Foo1 { + public Bar1 getBar() { + return new Bar1Impl(); + } + } + + public interface Bar1 { + } + + public static class Bar1Impl implements Bar1 { + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + converter = container.getInstance(XWorkConverter.class); + + ActionContext ac = ActionContext.getContext(); + ac.setLocale(Locale.US); + context = ac.getContextMap(); + stack = (OgnlValueStack) ac.getValueStack(); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/inject/ContainerImplTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/inject/ContainerImplTest.java new file mode 100644 index 000000000..2b3106dc4 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/inject/ContainerImplTest.java @@ -0,0 +1,119 @@ +package com.opensymphony.xwork2.inject; + +import junit.framework.TestCase; + +/** + * ContainerImpl Tester. + * + * @author Lukasz Lenart + * @version 1.0 + * @since

11/26/2008
+ */ +public class ContainerImplTest extends TestCase { + + private Container c; + + @Override + protected void setUp() throws Exception { + super.setUp(); + ContainerBuilder cb = new ContainerBuilder(); + cb.constant("methodCheck.name", "Lukasz"); + cb.constant("fieldCheck.name", "Lukasz"); + c = cb.create(false); + } + + /** + * Inject values into field + */ + public void testFieldInjector() throws Exception { + + FieldCheck fieldCheck = new FieldCheck(); + + try { + c.inject(fieldCheck); + assertTrue(true); + } catch (DependencyException expected) { + fail("No exception expected!"); + } + + assertEquals(fieldCheck.getName(), "Lukasz"); + } + + /** + * Inject values into method + */ + public void testMethodInjector() throws Exception { + + MethodCheck methodCheck = new MethodCheck(); + + try { + c.inject(methodCheck); + assertTrue(true); + } catch (DependencyException expected) { + fail("No exception expected!"); + } + } + + /** + * Inject values into field under SecurityManager + */ + public void testFieldInjectorWithSecurityEnabled() throws Exception { + + System.setSecurityManager(new SecurityManager()); + + FieldCheck fieldCheck = new FieldCheck(); + + try { + c.inject(fieldCheck); + assertEquals(fieldCheck.getName(), "Lukasz"); + fail("Exception should be thrown!"); + } catch (DependencyException expected) { + // that was expected + } + } + + /** + * Inject values into method under SecurityManager + */ + public void testMethodInjectorWithSecurityEnabled() throws Exception { + + // not needed, already set + //System.setSecurityManager(new SecurityManager()); + + MethodCheck methodCheck = new MethodCheck(); + + try { + c.inject(methodCheck); + assertEquals(methodCheck.getName(), "Lukasz"); + fail("Exception sould be thrown!"); + } catch (DependencyException expected) { + // that was expected + } + } + + class FieldCheck { + + @Inject("fieldCheck.name") + private String name; + + public String getName() { + return name; + } + } + + class MethodCheck { + + private String name; + + @Inject("methodCheck.name") + private void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java new file mode 100644 index 000000000..768fbcf29 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java @@ -0,0 +1,131 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.mock.MockActionProxy; + +import java.util.HashMap; +import java.util.Map; + + +/** + * AliasInterceptorTest + * + * Test of aliasInterceptor specifically depends on actionTest test defined in /test/xwork.xml + * stack.getContext().putAll(params); + *

+ * e.g. + * + * #{ "aliasSource" : "aliasDest", "bar":"baz" } + * + * + * + * + * @author Matthew Payne + */ +public class AliasInterceptorTest extends XWorkTestCase { + + public void testUsingDefaultInterceptorThatAliasPropertiesAreCopied() throws Exception { + Map params = new HashMap(); + params.put("aliasSource", "source here"); + + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + ActionProxy proxy = actionProxyFactory.createActionProxy("", "aliasTest", params); + SimpleAction actionOne = (SimpleAction) proxy.getAction(); + actionOne.setAliasSource("name to be copied"); + actionOne.setFoo(17); + actionOne.setBar(23); + proxy.execute(); + assertEquals(actionOne.getAliasSource(), actionOne.getAliasDest()); + } + + public void testInvalidAliasExpression() throws Exception { + Action action = new SimpleFooAction(); + MockActionInvocation mai = new MockActionInvocation(); + + MockActionProxy map = new MockActionProxy(); + + ActionConfig cfg = new ActionConfig.Builder("", "", "") + .addParam("aliases", "invalid alias expression") + .build(); + map.setConfig(cfg); + + mai.setProxy(map); + mai.setAction(action); + mai.setInvocationContext(ActionContext.getContext()); + + AliasInterceptor ai = new AliasInterceptor(); + ai.init(); + + ai.intercept(mai); + + ai.destroy(); + } + + public void testSetAliasKeys() throws Exception { + Action action = new SimpleFooAction(); + MockActionInvocation mai = new MockActionInvocation(); + + MockActionProxy map = new MockActionProxy(); + + ActionConfig cfg = new ActionConfig.Builder("", "", "") + .addParam("hello", "invalid alias expression") + .build(); + map.setConfig(cfg); + + mai.setProxy(map); + mai.setAction(action); + mai.setInvocationContext(ActionContext.getContext()); + + AliasInterceptor ai = new AliasInterceptor(); + ai.init(); + ai.setAliasesKey("hello"); + + ai.intercept(mai); + + ai.destroy(); + } + + public void testSetInvalidAliasKeys() throws Exception { + Action action = new SimpleFooAction(); + MockActionInvocation mai = new MockActionInvocation(); + + MockActionProxy map = new MockActionProxy(); + + ActionConfig cfg = new ActionConfig.Builder("", "", "") + .addParam("hello", "invalid alias expression") + .build(); + map.setConfig(cfg); + + mai.setProxy(map); + mai.setAction(action); + mai.setInvocationContext(ActionContext.getContext()); + + AliasInterceptor ai = new AliasInterceptor(); + ai.init(); + ai.setAliasesKey("iamnotinconfig"); + + ai.intercept(mai); + + ai.destroy(); + } + +} + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ChainingInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ChainingInterceptorTest.java new file mode 100644 index 000000000..d5396e750 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ChainingInterceptorTest.java @@ -0,0 +1,140 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; + + +/** + * Unit test for {@link ChainingInterceptor}. + * + * @author Jason Carreira + */ +public class ChainingInterceptorTest extends XWorkTestCase { + + ActionInvocation invocation; + ChainingInterceptor interceptor; + Mock mockInvocation; + ValueStack stack; + + + public void testActionErrorsCanBeAddedAfterChain() throws Exception { + SimpleAction action1 = new SimpleAction(); + SimpleAction action2 = new SimpleAction(); + action1.addActionError("foo"); + mockInvocation.matchAndReturn("getAction", action2); + stack.push(action1); + stack.push(action2); + interceptor.intercept(invocation); + assertEquals(action1.getActionErrors(), action2.getActionErrors()); + action2.addActionError("bar"); + assertEquals(1, action1.getActionErrors().size()); + assertEquals(2, action2.getActionErrors().size()); + assertTrue(action2.getActionErrors().contains("bar")); + } + + public void testPropertiesChained() throws Exception { + TestBean bean = new TestBean(); + TestBeanAction action = new TestBeanAction(); + mockInvocation.matchAndReturn("getAction", action); + bean.setBirth(new Date()); + bean.setName("foo"); + bean.setCount(1); + stack.push(bean); + stack.push(action); + interceptor.intercept(invocation); + assertEquals(bean.getBirth(), action.getBirth()); + assertEquals(bean.getName(), action.getName()); + assertEquals(bean.getCount(), action.getCount()); + } + + public void testExcludesPropertiesChained() throws Exception { + TestBean bean = new TestBean(); + TestBeanAction action = new TestBeanAction(); + mockInvocation.matchAndReturn("getAction", action); + bean.setBirth(new Date()); + bean.setName("foo"); + bean.setCount(1); + stack.push(bean); + stack.push(action); + + Collection excludes = new ArrayList(); + excludes.add("count"); + interceptor.setExcludes(excludes); + interceptor.intercept(invocation); + assertEquals(bean.getBirth(), action.getBirth()); + assertEquals(bean.getName(), action.getName()); + assertEquals(0, action.getCount()); + assertEquals(excludes, interceptor.getExcludes()); + } + + public void testTwoExcludesPropertiesChained() throws Exception { + TestBean bean = new TestBean(); + TestBeanAction action = new TestBeanAction(); + mockInvocation.matchAndReturn("getAction", action); + bean.setBirth(new Date()); + bean.setName("foo"); + bean.setCount(1); + stack.push(bean); + stack.push(action); + + Collection excludes = new ArrayList(); + excludes.add("name"); + excludes.add("count"); + interceptor.setExcludes(excludes); + interceptor.intercept(invocation); + assertEquals(bean.getBirth(), action.getBirth()); + assertEquals(null, action.getName()); + assertEquals(0, action.getCount()); + assertEquals(excludes, interceptor.getExcludes()); + } + + public void testNullCompoundRootElementAllowsProcessToContinue() throws Exception { + // we should not get NPE, but instead get a warning logged. + stack.push(null); + stack.push(null); + stack.push(null); + interceptor.intercept(invocation); + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + stack = ActionContext.getContext().getValueStack(); + mockInvocation = new Mock(ActionInvocation.class); + mockInvocation.expectAndReturn("getStack", stack); + mockInvocation.expectAndReturn("invoke", Action.SUCCESS); + mockInvocation.expectAndReturn("getInvocationContext", new ActionContext(new HashMap())); + invocation = (ActionInvocation) mockInvocation.proxy(); + interceptor = new ChainingInterceptor(); + container.inject(interceptor); + } + + + private class TestBeanAction extends TestBean implements Action { + public String execute() throws Exception { + return SUCCESS; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptorTest.java new file mode 100644 index 000000000..28c5007e8 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptorTest.java @@ -0,0 +1,108 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Unit test for {@link ConversionErrorInterceptor}. + * + * @author Jason Carreira + */ +public class ConversionErrorInterceptorTest extends XWorkTestCase { + + protected ActionContext context; + protected ActionInvocation invocation; + protected ConversionErrorInterceptor interceptor; + protected Map conversionErrors; + protected Mock mockInvocation; + protected ValueStack stack; + + + public void testFieldErrorAdded() throws Exception { + conversionErrors.put("foo", new Long(123)); + + SimpleAction action = new SimpleAction(); + mockInvocation.expectAndReturn("getAction", action); + stack.push(action); + mockInvocation.matchAndReturn("getAction", action); + assertNull(action.getFieldErrors().get("foo")); + interceptor.intercept(invocation); + assertTrue(action.hasFieldErrors()); + assertNotNull(action.getFieldErrors().get("foo")); + } + + public void testFieldErrorWithMapKeyAdded() throws Exception { + String fieldName = "foo['1'].intValue"; + conversionErrors.put(fieldName, "bar"); + ActionSupport action = new ActionSupport(); + mockInvocation.expectAndReturn("getAction", action); + stack.push(action); + mockInvocation.matchAndReturn("getAction", action); + assertNull(action.getFieldErrors().get(fieldName)); + interceptor.intercept(invocation); + assertTrue(action.hasFieldErrors()); // This fails! + assertNotNull(action.getFieldErrors().get(fieldName)); + } + + public void testWithPreResultListener() throws Exception { + conversionErrors.put("foo", "Hello"); + + ActionContext ac = new ActionContext(stack.getContext()); + ac.setConversionErrors(conversionErrors); + ac.setValueStack(stack); + + MockActionInvocation mai = new MockActionInvocation(); + mai.setInvocationContext(ac); + mai.setStack(stack); + SimpleAction action = new SimpleAction(); + action.setFoo(55); + mai.setAction(action); + stack.push(action); + assertNull(action.getFieldErrors().get("foo")); + assertEquals(new Integer(55), stack.findValue("foo")); + + interceptor.intercept(mai); + + assertTrue(action.hasFieldErrors()); + assertNotNull(action.getFieldErrors().get("foo")); + + assertEquals("Hello", stack.findValue("foo")); // assume that the original value is reset + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + interceptor = new ConversionErrorInterceptor(); + mockInvocation = new Mock(ActionInvocation.class); + invocation = (ActionInvocation) mockInvocation.proxy(); + stack = ActionContext.getContext().getValueStack(); + context = new ActionContext(stack.getContext()); + conversionErrors = new HashMap(); + context.setConversionErrors(conversionErrors); + mockInvocation.matchAndReturn("getInvocationContext", context); + mockInvocation.expect("addPreResultListener", C.isA(PreResultListener.class)); + mockInvocation.expectAndReturn("invoke", Action.SUCCESS); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptorTest.java new file mode 100644 index 000000000..713aaefe1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptorTest.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.interceptor; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.validator.ValidationInterceptor; + +import java.util.HashMap; + +import org.easymock.EasyMock; +import org.easymock.IAnswer; + + +/** + * Unit test for {@link DefaultWorkflowInterceptor}. + * + * @author Jason Carreira + */ +public class DefaultWorkflowInterceptorTest extends XWorkTestCase { + + DefaultWorkflowInterceptor interceptor; + private ActionInvocation invocation; + private Action action; + private ActionProxy proxy; + private ActionConfig config; + private String result = "testing123"; + + + public void testInvokesActionInvocationIfNoErrors() throws Exception { + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.intercept(invocation); + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testReturnsInputWithoutExecutingIfHasErrors() throws Exception { + result = Action.INPUT; + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.intercept(invocation); + assertEquals(Action.INPUT, interceptor.intercept(invocation)); + } + + public void testExcludesMethod() throws Exception { + interceptor.setExcludeMethods("execute"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("execute"); + interceptor.setExcludeMethods("execute"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testExcludesMethodWithWildCard() throws Exception { + interceptor.setExcludeMethods("*"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.intercept(invocation); + validationInterceptor.setExcludeMethods("*"); + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testIncludesMethodWithWildcard() throws Exception { + interceptor.setIncludeMethods("*"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setIncludeMethods("*"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + + public void testIncludesMethod() throws Exception { + interceptor.setIncludeMethods("execute"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setIncludeMethods("execute"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testIncludesAndExcludesMethod() throws Exception { + interceptor.setExcludeMethods("execute,input,validate"); + interceptor.setIncludeMethods("execute"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("execute,input,validate"); + validationInterceptor.setIncludeMethods("execute"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testIncludesAndExcludesMethodAllWildCarded() throws Exception { + interceptor.setExcludeMethods("*"); + interceptor.setIncludeMethods("*"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("*"); + validationInterceptor.setIncludeMethods("*"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testIncludesAndExcludesMethodWithExcludeWildcard() throws Exception { + interceptor.setExcludeMethods("*"); + interceptor.setIncludeMethods("execute"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("*"); + validationInterceptor.setIncludeMethods("execute"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testIncludesAndExcludesMethodWithIncludeWildcardAndNoMatches() throws Exception { + interceptor.setExcludeMethods("execute,input,validate"); + interceptor.setIncludeMethods("*"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("execute,input,validate"); + validationInterceptor.setIncludeMethods("*"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testIncludesAndExcludesMethodWithIncludeWildcard() throws Exception { + interceptor.setExcludeMethods("input,validate"); + interceptor.setIncludeMethods("*"); + + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("input,validate"); + validationInterceptor.setIncludeMethods("*"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + public void testNoValidateAction() throws Exception { + ValidationInterceptor validationInterceptor = create(); + validationInterceptor.setExcludeMethods("execute,input,validate"); + validationInterceptor.setIncludeMethods("execute"); + validationInterceptor.intercept(invocation); + + assertEquals(result, interceptor.intercept(invocation)); + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + config = new ActionConfig.Builder("", "name", "").build(); + action = EasyMock.createNiceMock(ValidateAction.class); + invocation = EasyMock.createNiceMock(ActionInvocation.class); + interceptor = new DefaultWorkflowInterceptor(); + proxy = EasyMock.createNiceMock(ActionProxy.class); + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(action).anyTimes(); + EasyMock.expect(invocation.invoke()).andAnswer(new IAnswer() { + public String answer() throws Throwable { + return result; + } + }).anyTimes(); + + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + EasyMock.expect(proxy.getMethod()).andReturn("execute").anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(action); + EasyMock.replay(proxy); + + ActionContext contex = new ActionContext(new HashMap()); + ActionContext.setContext(contex); + contex.setActionInvocation(invocation); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + protected ValidationInterceptor create() { + ObjectFactory objectFactory = container.getInstance(ObjectFactory.class); + return (ValidationInterceptor) objectFactory.buildInterceptor( + new InterceptorConfig.Builder("model", ValidationInterceptor.class.getName()).build(), new HashMap()); + } + + + + + private interface ValidateAction extends Action, Validateable, ValidationAware { + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptorTest.java new file mode 100644 index 000000000..37bcaae1c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptorTest.java @@ -0,0 +1,307 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.ValidationException; + +import java.util.HashMap; + +/** + * Unit test for ExceptionMappingInterceptor. + * + * @author Matthew E. Porter (matthew dot porter at metissian dot com) + */ +public class ExceptionMappingInterceptorTest extends XWorkTestCase { + + ActionInvocation invocation; + ExceptionMappingInterceptor interceptor; + Mock mockInvocation; + ValueStack stack; + + + public void testThrownExceptionMatching() throws Exception { + this.setUpWithExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new XWorkException("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + String result = interceptor.intercept(invocation); + assertNotNull(stack.findValue("exception")); + assertEquals(stack.findValue("exception"), exception); + assertEquals(result, "spooky"); + ExceptionHolder holder = (ExceptionHolder) stack.getRoot().get(0); // is on top of the root + assertNotNull(holder.getExceptionStack()); // to invoke the method for unit test + } + + public void testThrownExceptionMatching2() throws Exception { + this.setUpWithExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new ValidationException("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + String result = interceptor.intercept(invocation); + assertNotNull(stack.findValue("exception")); + assertEquals(stack.findValue("exception"), exception); + assertEquals(result, "throwable"); + } + + public void testNoThrownException() throws Exception { + this.setUpWithExceptionMappings(); + + Mock action = new Mock(Action.class); + mockInvocation.expectAndReturn("invoke", Action.SUCCESS); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + String result = interceptor.intercept(invocation); + assertEquals(result, Action.SUCCESS); + assertNull(stack.findValue("exception")); + } + + public void testThrownExceptionNoMatch() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLogging() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingCategory() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingCategoryLevelFatal() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.setLogLevel("fatal"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + + assertEquals("fatal", interceptor.getLogLevel()); + assertEquals(true, interceptor.isLogEnabled()); + assertEquals("showcase.unhandled", interceptor.getLogCategory()); + } + + public void testThrownExceptionNoMatchLoggingCategoryLevelError() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.setLogLevel("error"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingCategoryLevelWarn() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.setLogLevel("warn"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingCategoryLevelInfo() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.setLogLevel("info"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingCategoryLevelDebug() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.setLogLevel("debug"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingCategoryLevelTrace() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogCategory("showcase.unhandled"); + interceptor.setLogLevel("trace"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (Exception e) { + assertEquals(e, exception); + } + } + + public void testThrownExceptionNoMatchLoggingUnknownLevel() throws Exception { + this.setupWithoutExceptionMappings(); + + Mock action = new Mock(Action.class); + Exception exception = new Exception("test"); + mockInvocation.expectAndThrow("invoke", exception); + mockInvocation.matchAndReturn("getAction", ((Action) action.proxy())); + + try { + interceptor.setLogEnabled(true); + interceptor.setLogLevel("xxx"); + interceptor.intercept(invocation); + fail("Should not have reached this point."); + } catch (IllegalArgumentException e) { + // success + } + } + + private void setupWithoutExceptionMappings() { + ActionConfig actionConfig = new ActionConfig.Builder("", "", "").build(); + Mock actionProxy = new Mock(ActionProxy.class); + actionProxy.expectAndReturn("getConfig", actionConfig); + mockInvocation.expectAndReturn("getProxy", ((ActionProxy) actionProxy.proxy())); + invocation = (ActionInvocation) mockInvocation.proxy(); + } + + private void setUpWithExceptionMappings() { + ActionConfig actionConfig = new ActionConfig.Builder("", "", "") + .addExceptionMapping(new ExceptionMappingConfig.Builder("xwork", "com.opensymphony.xwork2.XWorkException", "spooky").build()) + .addExceptionMapping(new ExceptionMappingConfig.Builder("throwable", "java.lang.Throwable", "throwable").build()) + .build(); + Mock actionProxy = new Mock(ActionProxy.class); + actionProxy.expectAndReturn("getConfig", actionConfig); + mockInvocation.expectAndReturn("getProxy", ((ActionProxy) actionProxy.proxy())); + + invocation = (ActionInvocation) mockInvocation.proxy(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + stack = ActionContext.getContext().getValueStack(); + mockInvocation = new Mock(ActionInvocation.class); + mockInvocation.expectAndReturn("getStack", stack); + mockInvocation.expectAndReturn("getInvocationContext", new ActionContext(new HashMap())); + interceptor = new ExceptionMappingInterceptor(); + interceptor.init(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + interceptor.destroy(); + invocation = null; + interceptor = null; + mockInvocation = null; + stack = null; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/I18nInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/I18nInterceptorTest.java new file mode 100644 index 000000000..1b585ec24 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/I18nInterceptorTest.java @@ -0,0 +1,207 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.SimpleFooAction; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.io.Serializable; + +/** + * Unit test for I18nInterceptor. + * + * @author Claus Ibsen + */ +public class I18nInterceptorTest extends TestCase { + + private I18nInterceptor interceptor; + private ActionContext ac; + private Map params; + private Map session; + private ActionInvocation mai; + + public void testEmptyParamAndSession() throws Exception { + interceptor.intercept(mai); + } + + public void testNoSession() throws Exception { + ac.setSession(null); + interceptor.intercept(mai); + } + + public void testDefaultLocale() throws Exception { + params.put(I18nInterceptor.DEFAULT_PARAMETER, "_"); // bad locale that would get us default locale instead + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(Locale.getDefault(), session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object + } + + public void testDenmarkLocale() throws Exception { + params.put(I18nInterceptor.DEFAULT_PARAMETER, "da_DK"); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + Locale denmark = new Locale("da", "DK"); + assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object + } + + public void testDenmarkLocaleRequestOnly() throws Exception { + params.put(I18nInterceptor.DEFAULT_REQUESTONLY_PARAMETER, "da_DK"); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + Locale denmark = new Locale("da", "DK"); + assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(denmark, mai.getInvocationContext().getLocale()); // should create a locale object + } + + public void testCountryOnlyLocale() throws Exception { + params.put(I18nInterceptor.DEFAULT_PARAMETER, "DK"); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + Locale denmark = new Locale("DK"); + assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object + } + + public void testLanguageOnlyLocale() throws Exception { + params.put(I18nInterceptor.DEFAULT_PARAMETER, "da_"); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + Locale denmark = new Locale("da"); + assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object + } + + public void testWithVariant() throws Exception { + params.put(I18nInterceptor.DEFAULT_PARAMETER, "fr_CA_xx"); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + Locale variant = new Locale("fr", "CA", "xx"); + Locale locale = (Locale) session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE); + assertNotNull(locale); // should be stored here + assertEquals(variant, locale); + assertEquals("xx", locale.getVariant()); + } + + public void testWithVariantRequestOnly() throws Exception { + params.put(I18nInterceptor.DEFAULT_REQUESTONLY_PARAMETER, "fr_CA_xx"); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); + + Locale variant = new Locale("fr", "CA", "xx"); + Locale locale = mai.getInvocationContext().getLocale(); + assertNotNull(locale); // should be stored here + assertEquals(variant, locale); + assertEquals("xx", locale.getVariant()); + } + + public void testRealLocaleObjectInParams() throws Exception { + params.put(I18nInterceptor.DEFAULT_PARAMETER, Locale.CANADA_FRENCH); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(Locale.CANADA_FRENCH, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object + } + + public void testRealLocalesInParams() throws Exception { + Locale[] locales = new Locale[] { Locale.CANADA_FRENCH }; + assertTrue(locales.getClass().isArray()); + params.put(I18nInterceptor.DEFAULT_PARAMETER, locales); + interceptor.intercept(mai); + + assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed + + assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here + assertEquals(Locale.CANADA_FRENCH, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); + } + + public void testSetParameterAndAttributeNames() throws Exception { + interceptor.setAttributeName("hello"); + interceptor.setParameterName("world"); + + params.put("world", Locale.CHINA); + interceptor.intercept(mai); + + assertNull(params.get("world")); // should have been removed + + assertNotNull(session.get("hello")); // should be stored here + assertEquals(Locale.CHINA, session.get("hello")); + } + + public void testActionContextLocaleIsPreservedWhenNotOverridden() throws Exception { + final Locale locale1 = Locale.TRADITIONAL_CHINESE; + mai.getInvocationContext().setLocale(locale1); + interceptor.intercept(mai); + + Locale locale = (Locale) session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE); + assertNull(locale); // should not be stored here + locale = mai.getInvocationContext().getLocale(); + assertEquals(locale1, locale); + } + + @Override + protected void setUp() throws Exception { + interceptor = new I18nInterceptor(); + interceptor.init(); + params = new HashMap(); + session = new HashMap(); + + Map ctx = new HashMap(); + ctx.put(ActionContext.PARAMETERS, params); + ctx.put(ActionContext.SESSION, session); + ac = new ActionContext(ctx); + + Action action = new SimpleFooAction(); + mai = new MockActionInvocation(); + ((MockActionInvocation) mai).setAction(action); + ((MockActionInvocation) mai).setInvocationContext(ac); + } + + @Override + protected void tearDown() throws Exception { + interceptor.destroy(); + interceptor = null; + ac = null; + params = null; + session = null; + mai = null; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtilTest.java new file mode 100644 index 000000000..98de1ceee --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtilTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-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.interceptor; + +import com.opensymphony.xwork2.XWorkTestCase; + +import java.util.HashSet; + +public class MethodFilterInterceptorUtilTest extends XWorkTestCase { + + public void testApplyMethodNoWildcards() { + + HashSet included= new HashSet(); + included.add("included"); + included.add("includedAgain"); + + HashSet excluded= new HashSet(); + excluded.add("excluded"); + excluded.add("excludedAgain"); + + // test expected behavior + assertFalse(MethodFilterInterceptorUtil.applyMethod(excluded, included, "excluded")); + assertTrue(MethodFilterInterceptorUtil.applyMethod(excluded, included, "included")); + + // test precedence + included.add("excluded"); + assertTrue(MethodFilterInterceptorUtil.applyMethod(excluded, included, "excluded")); + + } + + public void testApplyMethodWithWildcards() { + + HashSet included= new HashSet(); + included.add("included*"); + + HashSet excluded= new HashSet(); + excluded.add("excluded*"); + + assertTrue(MethodFilterInterceptorUtil.applyMethod(excluded, included, "includedMethod")); + assertFalse(MethodFilterInterceptorUtil.applyMethod(excluded, included, "excludedMethod")); + + // test precedence + included.clear(); + excluded.clear(); + included.add("wildIncluded"); + excluded.add("wild*"); + + assertTrue(MethodFilterInterceptorUtil.applyMethod(excluded, included, "wildIncluded")); + assertFalse(MethodFilterInterceptorUtil.applyMethod(excluded, included, "wildNotIncluded")); + + // test precedence + included.clear(); + excluded.clear(); + included.add("*"); + excluded.add("excluded"); + + assertTrue(MethodFilterInterceptorUtil.applyMethod(excluded, included, "anyMethod")); + + // test precedence + included.clear(); + excluded.clear(); + included.add("included"); + excluded.add("*"); + + assertTrue(MethodFilterInterceptorUtil.applyMethod(excluded, included, "included")); + assertFalse(MethodFilterInterceptorUtil.applyMethod(excluded, included, "shouldBeExcluded")); + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptorTest.java new file mode 100644 index 000000000..701d020de --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptorTest.java @@ -0,0 +1,112 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.ConstraintMatcher; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.Date; + + +/** + * @author $Author$ + * @version $Revision$ + */ +public class ModelDrivenInterceptorTest extends XWorkTestCase { + + Action action; + Mock mockActionInvocation; + ModelDrivenInterceptor modelDrivenInterceptor; + Object model; + PreResultListener preResultListener; + + + public void testModelDrivenGetsPushedOntoStack() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + action = new ModelDrivenAction(); + mockActionInvocation.expectAndReturn("getAction", action); + mockActionInvocation.expectAndReturn("getStack", stack); + mockActionInvocation.expectAndReturn("invoke", "foo"); + + modelDrivenInterceptor.intercept((ActionInvocation) mockActionInvocation.proxy()); + + Object topOfStack = stack.pop(); + assertEquals("our model should be on the top of the stack", model, topOfStack); + } + + public void testModelDrivenUpdatedAndGetsPushedOntoStack() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + action = new ModelDrivenAction(); + mockActionInvocation.expectAndReturn("getAction", action); + mockActionInvocation.matchAndReturn("getStack", stack); + mockActionInvocation.expectAndReturn("invoke", "foo"); + mockActionInvocation.expect("addPreResultListener", new ConstraintMatcher() { + + public boolean matches(Object[] objects) { + preResultListener = (PreResultListener) objects[0]; + return true; + } + + public Object[] getConstraints() { + return new Object[0]; //To change body of implemented methods use File | Settings | File Templates. + } + }); + modelDrivenInterceptor.setRefreshModelBeforeResult(true); + + modelDrivenInterceptor.intercept((ActionInvocation) mockActionInvocation.proxy()); + assertNotNull(preResultListener); + model = "this is my model"; + preResultListener.beforeResult((ActionInvocation) mockActionInvocation.proxy(), "success"); + + Object topOfStack = stack.pop(); + assertEquals("our model should be on the top of the stack", model, topOfStack); + assertEquals(1, stack.getRoot().size()); + } + + public void testStackNotModifedForNormalAction() throws Exception { + action = new ActionSupport(); + mockActionInvocation.expectAndReturn("getAction", action); + mockActionInvocation.expectAndReturn("invoke", "foo"); + + // nothing should happen + modelDrivenInterceptor.intercept((ActionInvocation) mockActionInvocation.proxy()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mockActionInvocation = new Mock(ActionInvocation.class); + modelDrivenInterceptor = new ModelDrivenInterceptor(); + model = new Date(); // any object will do + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mockActionInvocation.verify(); + } + + + public class ModelDrivenAction extends ActionSupport implements ModelDriven { + + public Object getModel() { + return model; + } + + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptorTest.java new file mode 100644 index 000000000..c1dbb3832 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptorTest.java @@ -0,0 +1,125 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Unit test for {@link ParameterFilterInterceptor}. + * + * @author Gabe + */ +public class ParameterFilterInterceptorTest extends XWorkTestCase { + + ActionInvocation invocation; + ParameterFilterInterceptor interceptor; + Mock mockInvocation; + ValueStack stack; + Map contextMap; + + @Override + protected void setUp() throws Exception { + super.setUp(); + contextMap=new HashMap(); + stack = ActionContext.getContext().getValueStack(); + mockInvocation = new Mock(ActionInvocation.class); + mockInvocation.expectAndReturn("getStack", stack); + mockInvocation.expectAndReturn("invoke", Action.SUCCESS); + mockInvocation.expectAndReturn("getInvocationContext", new ActionContext(contextMap)); + mockInvocation.matchAndReturn("getAction", new SimpleAction()); + invocation = (ActionInvocation) mockInvocation.proxy(); + interceptor = new ParameterFilterInterceptor(); + interceptor.init(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + interceptor.destroy(); + } + + public void testBasicBlockAll() throws Exception { + runFilterTest(null,null,true,new String[] {"blah", "bladeblah", "bladebladeblah"}); + assertEquals(0, getParameterNames().size()); + } + + public void testBasicAllowed() throws Exception { + runFilterTest("blah",null,true,new String[] {"blah"}); + assertEquals(1, getParameterNames().size()); + } + + public void testBasicBlocked() throws Exception { + runFilterTest(null,"blah",false,new String[] {"blah"}); + assertEquals(0, getParameterNames().size()); + } + public void testAllSubpropertiesBlocked() throws Exception { + runFilterTest(null,"blah",false,new String[] {"blah.deblah", "blah.somethingelse", "blah(22)"}); + assertEquals(0, getParameterNames().size()); + } + + public void testAllSubpropertiesAllowed() throws Exception { + runFilterTest("blah",null,true, + new String[] {"blah.deblah", "blah.somethingelse", "blah(22)"}); + assertEquals(3, getParameterNames().size()); + } + + public void testTreeBlocking() throws Exception { + runFilterTest("blah.deblah","blah,blah.deblah.deblah",false, + new String[] {"blah", "blah.deblah", "blah.deblah.deblah"}); + Collection paramNames=getParameterNames(); + assertEquals(1, paramNames.size()); + assertEquals(paramNames.iterator().next(),"blah.deblah"); + } + + public void testEnsureOnlyPropsBlocked() throws Exception { + runFilterTest(null,"blah",false,new String[] {"blahdeblah"}); + assertEquals(1, getParameterNames().size()); + } + + + private void runFilterTest(String allowed, String blocked, boolean defaultBlocked, String[] paramNames) throws Exception { + interceptor.setAllowed(allowed); + interceptor.setBlocked(blocked); + interceptor.setDefaultBlock(defaultBlocked); + setUpParameters(paramNames); + runAction(); + + } + + private void setUpParameters(String [] paramNames) { + Map params=new HashMap(); + for (String paramName : paramNames) { + params.put(paramName, "irrelevant what this is"); + + } + contextMap.put(ActionContext.PARAMETERS, params); + } + + private Collection getParameterNames() { + return ((Map)contextMap.get(ActionContext.PARAMETERS)).keySet(); + } + + public void runAction() throws Exception { + interceptor.intercept(invocation); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptorTest.java new file mode 100644 index 000000000..14bccd03a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptorTest.java @@ -0,0 +1,116 @@ +package com.opensymphony.xwork2.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionSupport; +import junit.framework.TestCase; +import org.easymock.MockControl; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author tmjee + * @version $Date$ $Id$ + */ +public class ParameterRemoverInterceptorTest extends TestCase { + + protected Map contextMap; + protected ActionContext context; + protected MockControl actionInvocationControl; + protected ActionInvocation actionInvocation; + + @Override + protected void setUp() throws Exception { + contextMap = new LinkedHashMap(); + context = new ActionContext(contextMap); + + actionInvocationControl = MockControl.createControl(ActionInvocation.class); + actionInvocation = (ActionInvocation) actionInvocationControl.getMock(); + actionInvocationControl.expectAndDefaultReturn(actionInvocation.getAction(), new SampleAction()); + actionInvocationControl.expectAndDefaultReturn(actionInvocation.getInvocationContext(), context); + actionInvocationControl.expectAndDefaultReturn(actionInvocation.invoke(), "success"); + } + + public void testInterception1() throws Exception { + contextMap.put(ActionContext.PARAMETERS, new LinkedHashMap() { + private static final long serialVersionUID = 0L; + { + put("param1", new String[] { "paramValue1" }); + put("param2", new String[] { "paramValue2" }); + put("param3", new String[] { "paramValue3" }); + put("param", new String[] { "paramValue" }); + } + }); + + actionInvocationControl.replay(); + + ParameterRemoverInterceptor interceptor = new ParameterRemoverInterceptor(); + interceptor.setParamNames("param1,param2"); + interceptor.setParamValues("paramValue1,paramValue2"); + interceptor.intercept(actionInvocation); + + Map params = (Map) contextMap.get(ActionContext.PARAMETERS); + assertEquals(params.size(), 2); + assertTrue(params.containsKey("param3")); + assertTrue(params.containsKey("param")); + assertEquals(((String[])params.get("param3"))[0], "paramValue3"); + assertEquals(((String[])params.get("param"))[0], "paramValue"); + + actionInvocationControl.verify(); + } + + + public void testInterception2() throws Exception { + contextMap.put(ActionContext.PARAMETERS, new LinkedHashMap() { + private static final long serialVersionUID = 0L; + { + put("param1", new String[] { "paramValue2" }); + put("param2", new String[] { "paramValue1" }); + } + }); + + actionInvocationControl.replay(); + + ParameterRemoverInterceptor interceptor = new ParameterRemoverInterceptor(); + interceptor.setParamNames("param1,param2"); + interceptor.setParamValues("paramValue1,paramValue2"); + interceptor.intercept(actionInvocation); + + Map params = (Map) contextMap.get(ActionContext.PARAMETERS); + assertEquals(params.size(), 0); + + actionInvocationControl.verify(); + } + + + public void testInterception3() throws Exception { + contextMap.put(ActionContext.PARAMETERS, new LinkedHashMap() { + private static final long serialVersionUID = 0L; + { + put("param1", new String[] { "paramValueOne" }); + put("param2", new String[] { "paramValueTwo" }); + } + }); + + actionInvocationControl.replay(); + + ParameterRemoverInterceptor interceptor = new ParameterRemoverInterceptor(); + interceptor.setParamNames("param1,param2"); + interceptor.setParamValues("paramValue1,paramValue2"); + interceptor.intercept(actionInvocation); + + Map params = (Map) contextMap.get(ActionContext.PARAMETERS); + assertEquals(params.size(), 2); + assertTrue(params.containsKey("param1")); + assertTrue(params.containsKey("param2")); + assertEquals(((String[])params.get("param1"))[0], "paramValueOne"); + assertEquals(((String[])params.get("param2"))[0], "paramValueTwo"); + + actionInvocationControl.verify(); + } + + class SampleAction extends ActionSupport { + private static final long serialVersionUID = 7489487258845368260L; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java new file mode 100644 index 000000000..f0a264a54 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java @@ -0,0 +1,480 @@ +/* + * 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.interceptor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import ognl.PropertyAccessor; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ModelDrivenAction; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.TestBean; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.ognl.OgnlValueStackFactory; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; + + +/** + * Unit test for {@link ParametersInterceptor}. + * + * @author Jason Carreira + */ +public class ParametersInterceptorTest extends XWorkTestCase { + + public void testParameterNameAware() { + ParametersInterceptor pi = new ParametersInterceptor(); + container.inject(pi); + final Map actual = new HashMap(); + pi.setValueStackFactory(createValueStackFactory(actual)); + ValueStack stack = createStubValueStack(actual); + final Map expected = new HashMap() { + { + put("fooKey", "fooValue"); + put("barKey", "barValue"); + } + }; + Object a = new ParameterNameAware() { + public boolean acceptableParameterName(String parameterName) { + return expected.containsKey(parameterName); + } + }; + Map parameters = new HashMap() { + { + put("fooKey", "fooValue"); + put("barKey", "barValue"); + put("error", "error"); + } + }; + pi.setParameters(a, stack, parameters); + assertEquals(expected, actual); + } + + public void testDoesNotAllowMethodInvocations() throws Exception { + Map params = new HashMap(); + params.put("@java.lang.System@exit(1).dummy", "dumb value"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.MODEL_DRIVEN_PARAM_TEST, extraContext); + assertEquals(Action.SUCCESS, proxy.execute()); + + ModelDrivenAction action = (ModelDrivenAction) proxy.getAction(); + TestBean model = (TestBean) action.getModel(); + + String property = System.getProperty("xwork.security.test"); + assertNull(property); + } + + public void testModelDrivenParameters() throws Exception { + Map params = new HashMap(); + final String fooVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.foo"; + params.put("foo", fooVal); + + final String nameVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.name"; + params.put("name", nameVal); + params.put("count", "15"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.MODEL_DRIVEN_PARAM_TEST, extraContext); + assertEquals(Action.SUCCESS, proxy.execute()); + + ModelDrivenAction action = (ModelDrivenAction) proxy.getAction(); + TestBean model = (TestBean) action.getModel(); + assertEquals(nameVal, model.getName()); + assertEquals(15, model.getCount()); + assertEquals(fooVal, action.getFoo()); + } + + public void testParametersDoesNotAffectSession() throws Exception { + Map params = new HashMap(); + params.put("blah", "This is blah"); + params.put("#session.foo", "Foo"); + params.put("\u0023session[\'user\']", "0wn3d"); + params.put("\\u0023session[\'user\']", "0wn3d"); + params.put("\u0023session.user2", "0wn3d"); + params.put("\\u0023session.user2", "0wn3d"); + params.put("('\u0023'%20%2b%20'session[\'user3\']')(unused)", "0wn3d"); + params.put("('\\u0023' + 'session[\\'user4\\']')(unused)", "0wn3d"); + params.put("('\u0023'%2b'session[\'user5\']')(unused)", "0wn3d"); + params.put("('\\u0023'%2b'session[\'user5\']')(unused)", "0wn3d"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + ValueStack stack = proxy.getInvocation().getStack(); + HashMap session = new HashMap(); + stack.getContext().put("session", session); + proxy.execute(); + assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah()); + assertNull(session.get("foo")); + assertNull(session.get("user")); + assertNull(session.get("user2")); + assertNull(session.get("user3")); + assertNull(session.get("user4")); + assertNull(session.get("user5")); + } + + public void testParameters() throws Exception { + Map params = new HashMap(); + params.put("blah", "This is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah()); + } + + public void testParametersWithSpacesInTheName() throws Exception { + Map params = new HashMap(); + params.put("theProtectedMap['p0 p1']", "test1"); + params.put("theProtectedMap['p0p1 ']", "test2"); + params.put("theProtectedMap[' p0p1 ']", "test3"); + params.put("theProtectedMap[' p0 p1 ']", "test4"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + Map existingMap = ((SimpleAction) proxy.getAction()).getTheProtectedMap(); + assertEquals(4, existingMap.size()); + assertEquals("test1", existingMap.get("p0 p1")); + assertEquals("test2", existingMap.get("p0p1 ")); + assertEquals("test3", existingMap.get(" p0p1 ")); + assertEquals("test4", existingMap.get(" p0 p1 ")); + } + + public void testExcludedTrickyParameters() throws Exception { + Map params = new HashMap() { + { + put("blah", "This is blah"); + put("name", "try_1"); + put("(name)", "try_2"); + put("['name']", "try_3"); + put("['na' + 'me']", "try_4"); + put("{name}[0]", "try_5"); + put("(new string{'name'})[0]", "try_6"); + put("#{key: 'name'}.key", "try_7"); + + } + }; + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + + ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); + ParametersInterceptor pi =(ParametersInterceptor) config.getInterceptors().get(0).getInterceptor(); + pi.setExcludeParams("name"); + + proxy.execute(); + + SimpleAction action = (SimpleAction) proxy.getAction(); + assertNull(action.getName()); + assertEquals("This is blah", (action).getBlah()); + } + + public void testAcceptedTrickyParameters() throws Exception { + Map params = new HashMap() { + { + put("blah", "This is blah"); + put("baz", "123"); + put("name", "try_1"); + put("(name)", "try_2"); + put("['name']", "try_3"); + put("['na' + 'me']", "try_4"); + put("{name}[0]", "try_5"); + put("(new string{'name'})[0]", "try_6"); + put("#{key: 'name'}.key", "try_7"); + } + }; + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + + ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); + ParametersInterceptor pi =(ParametersInterceptor) config.getInterceptors().get(0).getInterceptor(); + pi.setAcceptParamNames("blah, baz"); + + proxy.execute(); + + SimpleAction action = (SimpleAction) proxy.getAction(); + assertNull(action.getName()); + assertEquals("This is blah", (action).getBlah()); + assertEquals(123, action.getBaz()); + } + + + public void testParametersNotAccessPrivateVariables() throws Exception { + Map params = new HashMap(); + params.put("protectedMap.foo", "This is blah"); + params.put("theProtectedMap.boo", "This is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + SimpleAction action = (SimpleAction) proxy.getAction(); + assertEquals(1, action.getTheProtectedMap().size()); + assertNotNull(action.getTheProtectedMap().get("boo")); + assertNull(action.getTheProtectedMap().get("foo")); + } + + public void testParametersNotAccessProtectedMethods() throws Exception { + Map params = new HashMap(); + params.put("theSemiProtectedMap.foo", "This is blah"); + params.put("theProtectedMap.boo", "This is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + SimpleAction action = (SimpleAction) proxy.getAction(); + assertEquals(1, action.getTheProtectedMap().size()); + assertNotNull(action.getTheProtectedMap().get("boo")); + assertNull(action.getTheProtectedMap().get("foo")); + } + + public void testParametersOverwriteField() throws Exception { + Map params = new LinkedHashMap(); + params.put("existingMap.boo", "This is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + SimpleAction action = (SimpleAction) proxy.getAction(); + assertEquals(1, action.getTheExistingMap().size()); + assertNotNull(action.getTheExistingMap().get("boo")); + assertNull(action.getTheExistingMap().get("existingKey")); + } + + public void testNonexistentParametersGetLoggedInDevMode() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), + new MockConfigurationProvider(Collections.singletonMap("devMode", "true"))); + Map params = new HashMap(); + params.put("not_a_property", "There is no action property named like this"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + ParametersInterceptor.setDevMode("true"); + + ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); + container.inject(config.getInterceptors().get(0).getInterceptor()); + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0]; + assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'")); + } + + public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), + new MockConfigurationProvider(Collections.singletonMap("devMode", "false"))); + Map params = new HashMap(); + params.put("not_a_property", "There is no action property named like this"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); + container.inject(config.getInterceptors().get(0).getInterceptor()); + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((SimpleAction) proxy.getAction()).getActionMessages().isEmpty()); + } + + public void testNoParametersAction() throws Exception { + ParametersInterceptor interceptor = new ParametersInterceptor(); + interceptor.init(); + + MockActionInvocation mai = new MockActionInvocation(); + Action action = new NoParametersAction(); + mai.setAction(action); + + interceptor.doIntercept(mai); + interceptor.destroy(); + } + + public void testNoOrdered() throws Exception { + ParametersInterceptor pi = new ParametersInterceptor(); + container.inject(pi); + final Map actual = new LinkedHashMap(); + pi.setValueStackFactory(createValueStackFactory(actual)); + ValueStack stack = createStubValueStack(actual); + + Map parameters = new HashMap(); + parameters.put("user.address.city", "London"); + parameters.put("user.name", "Superman"); + + Action action = new SimpleAction(); + pi.setParameters(action, stack, parameters); + + assertEquals("ordered should be false by default", false, pi.isOrdered()); + assertEquals(2, actual.size()); + assertEquals("London", actual.get("user.address.city")); + assertEquals("Superman", actual.get("user.name")); + + // is not ordered + List values = new ArrayList(actual.values()); + assertEquals("London", values.get(0)); + assertEquals("Superman", values.get(1)); + } + + public void testOrdered() throws Exception { + ParametersInterceptor pi = new ParametersInterceptor(); + pi.setOrdered(true); + container.inject(pi); + final Map actual = new LinkedHashMap(); + pi.setValueStackFactory(createValueStackFactory(actual)); + ValueStack stack = createStubValueStack(actual); + + Map parameters = new HashMap(); + parameters.put("user.address.city", "London"); + parameters.put("user.name", "Superman"); + + Action action = new SimpleAction(); + pi.setParameters(action, stack, parameters); + + assertEquals(true, pi.isOrdered()); + assertEquals(2, actual.size()); + assertEquals("London", actual.get("user.address.city")); + assertEquals("Superman", actual.get("user.name")); + + // should be ordered so user.name should be first + List values = new ArrayList(actual.values()); + assertEquals("Superman", values.get(0)); + assertEquals("London", values.get(1)); + } + + public void testSetOrdered() throws Exception { + ParametersInterceptor pi = new ParametersInterceptor(); + container.inject(pi); + assertEquals("ordered should be false by default", false, pi.isOrdered()); + pi.setOrdered(true); + assertEquals(true, pi.isOrdered()); + } + + public void testExcludedParametersAreIgnored() throws Exception { + ParametersInterceptor pi = new ParametersInterceptor(); + container.inject(pi); + pi.setExcludeParams("dojo\\..*"); + final Map actual = new HashMap(); + pi.setValueStackFactory(createValueStackFactory(actual)); + ValueStack stack = createStubValueStack(actual); + container.inject(stack); + + final Map expected = new HashMap() { + { + put("fooKey", "fooValue"); + } + }; + + Map parameters = new HashMap() { + { + put("dojo.test", "dojoValue"); + put("fooKey", "fooValue"); + } + }; + pi.setParameters(new NoParametersAction(), stack, parameters); + assertEquals(expected, actual); + } + + private ValueStackFactory createValueStackFactory(final Map context) { + OgnlValueStackFactory factory = new OgnlValueStackFactory() { + @Override + public ValueStack createValueStack(ValueStack stack) { + return createStubValueStack(context); + } + }; + container.inject(factory); + return factory; + } + + private ValueStack createStubValueStack(final Map actual) { + ValueStack stack = new OgnlValueStack( + container.getInstance(XWorkConverter.class), + (CompoundRootAccessor)container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), + container.getInstance(TextProvider.class, "system"), true) { + @Override + public void setValue(String expr, Object value) { + actual.put(expr, value); + } + }; + container.inject(stack); + return stack; + } + + /* + public void testIndexedParameters() throws Exception { + Map params = new HashMap(); + params.put("indexedProp[33]", "This is blah"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); + proxy.execute(); + assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getIndexedProp(33)); + } + */ + + + private class NoParametersAction implements Action, NoParameters { + + public String execute() throws Exception { + return SUCCESS; + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider()); + + ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); + container.inject(config.getInterceptors().get(0).getInterceptor()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PreResultListenerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PreResultListenerTest.java new file mode 100644 index 000000000..fce27b861 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PreResultListenerTest.java @@ -0,0 +1,119 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +import java.util.HashMap; + + +/** + * PreResultListenerTest + * + * @author Jason Carreira + * Date: Nov 13, 2003 11:16:43 PM + */ +public class PreResultListenerTest extends XWorkTestCase { + + private int count = 1; + + + public void testPreResultListenersAreCalled() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("package", "action", new HashMap(), false, true); + ActionInvocation invocation = proxy.getInvocation(); + Mock preResultListenerMock1 = new Mock(PreResultListener.class); + preResultListenerMock1.expect("beforeResult", C.args(C.eq(invocation), C.eq(Action.SUCCESS))); + invocation.addPreResultListener((PreResultListener) preResultListenerMock1.proxy()); + proxy.execute(); + preResultListenerMock1.verify(); + } + + public void testPreResultListenersAreCalledInOrder() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("package", "action", new HashMap(), false, true); + ActionInvocation invocation = proxy.getInvocation(); + CountPreResultListener listener1 = new CountPreResultListener(); + CountPreResultListener listener2 = new CountPreResultListener(); + invocation.addPreResultListener(listener1); + invocation.addPreResultListener(listener2); + proxy.execute(); + assertNotNull(listener1.getMyOrder()); + assertNotNull(listener2.getMyOrder()); + assertEquals(listener1.getMyOrder().intValue() + 1, listener2.getMyOrder().intValue()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + loadConfigurationProviders(new ConfigurationProvider() { + Configuration configuration; + public void destroy() { + } + + public void init(Configuration config) { + this.configuration = config; + } + + public void loadPackages() { + PackageConfig packageConfig = new PackageConfig.Builder("package") + .addActionConfig("action", new ActionConfig.Builder("package", "action", SimpleFooAction.class.getName()).build()) + .build(); + configuration.addPackageConfig("package", packageConfig); + } + + /** + * Tells whether the ConfigurationProvider should reload its configuration + * + * @return + */ + public boolean needsReload() { + return false; + } + + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + builder.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class); + builder.factory(ObjectFactory.class); + + } + }); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + + private class CountPreResultListener implements PreResultListener { + private Integer myOrder = null; + + public Integer getMyOrder() { + return myOrder; + } + + public void beforeResult(ActionInvocation invocation, String resultCode) { + myOrder = new Integer(count++); + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtilTest.java new file mode 100644 index 000000000..fa02c8b6b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtilTest.java @@ -0,0 +1,292 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import junit.framework.TestCase; +import org.easymock.MockControl; + +import java.lang.reflect.Method; + +/** + * Test case for PrefixMethodInovcationUtil. + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class PrefixMethodInvocationUtilTest extends TestCase { + + // === capitalizeMethodName === + public void testCapitalizeMethodName() throws Exception { + assertEquals("SomeMethod", + PrefixMethodInvocationUtil.capitalizeMethodName("someMethod")); + assertEquals("AnotherMethod", + PrefixMethodInvocationUtil.capitalizeMethodName("anotherMethod")); + } + + // === getPrefixMethod === + public void testGetPrefixMethod1() throws Exception { + Object action = new PrefixMethodInvocationUtilTest.Action1(); + Method m = PrefixMethodInvocationUtil.getPrefixedMethod( + new String[] { "prepare", "prepareDo" }, "save", action); + assertNotNull(m); + assertEquals(m.getName(), "prepareSave"); + } + + public void testGetPrefixMethod2() throws Exception { + Object action = new PrefixMethodInvocationUtilTest.Action1(); + Method m = PrefixMethodInvocationUtil.getPrefixedMethod( + new String[] { "prepare", "prepareDo" }, "submit", action); + assertNotNull(m); + assertEquals(m.getName(), "prepareSubmit"); + } + + public void testGetPrefixMethod3() throws Exception { + Object action = new PrefixMethodInvocationUtilTest.Action1(); + Method m = PrefixMethodInvocationUtil.getPrefixedMethod( + new String[] { "prepare", "prepareDo" }, "cancel", action); + assertNotNull(m); + assertEquals(m.getName(), "prepareDoCancel"); + } + + public void testGetPrefixMethod4() throws Exception { + Object action = new PrefixMethodInvocationUtilTest.Action1(); + Method m = PrefixMethodInvocationUtil.getPrefixedMethod( + new String[] { "prepare", "prepareDo" }, "noSuchMethod", action); + assertNull(m); + } + + public void testGetPrefixMethod5() throws Exception { + Object action = new PrefixMethodInvocationUtilTest.Action1(); + Method m = PrefixMethodInvocationUtil.getPrefixedMethod( + new String[] { "noSuchPrefix", "noSuchPrefixDo" }, "save", action); + assertNull(m); + } + + + // === invokePrefixMethod === + public void testInvokePrefixMethod1() throws Exception { + PrefixMethodInvocationUtilTest.Action1 action = new PrefixMethodInvocationUtilTest.Action1(); + + // ActionProxy + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setReturnValue("save"); + + + // ActionInvocation + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setReturnValue(action); + mockActionInvocation.getProxy(); + controlActionInvocation.setReturnValue(mockActionProxy); + + controlActionProxy.replay(); + controlActionInvocation.replay(); + + + PrefixMethodInvocationUtil.invokePrefixMethod( + mockActionInvocation, + new String[] { "prepare", "prepareDo" }); + + controlActionProxy.verify(); + controlActionInvocation.verify(); + + assertTrue(action.prepareSaveInvoked); + assertFalse(action.prepareDoSaveInvoked); + assertFalse(action.prepareSubmitInvoked); + assertFalse(action.prepareDoCancelInvoked); + } + + public void testInvokePrefixMethod2() throws Exception { + PrefixMethodInvocationUtilTest.Action1 action = new PrefixMethodInvocationUtilTest.Action1(); + + // ActionProxy + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setReturnValue("submit"); + + + // ActionInvocation + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setReturnValue(action); + mockActionInvocation.getProxy(); + controlActionInvocation.setReturnValue(mockActionProxy); + + controlActionProxy.replay(); + controlActionInvocation.replay(); + + + PrefixMethodInvocationUtil.invokePrefixMethod( + mockActionInvocation, + new String[] { "prepare", "prepareDo" }); + + controlActionProxy.verify(); + controlActionInvocation.verify(); + + assertFalse(action.prepareSaveInvoked); + assertFalse(action.prepareDoSaveInvoked); + assertTrue(action.prepareSubmitInvoked); + assertFalse(action.prepareDoCancelInvoked); + } + + public void testInvokePrefixMethod3() throws Exception { + PrefixMethodInvocationUtilTest.Action1 action = new PrefixMethodInvocationUtilTest.Action1(); + + // ActionProxy + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setReturnValue("cancel"); + + + // ActionInvocation + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setReturnValue(action); + mockActionInvocation.getProxy(); + controlActionInvocation.setReturnValue(mockActionProxy); + + controlActionProxy.replay(); + controlActionInvocation.replay(); + + + PrefixMethodInvocationUtil.invokePrefixMethod( + mockActionInvocation, + new String[] { "prepare", "prepareDo" }); + + controlActionProxy.verify(); + controlActionInvocation.verify(); + + assertFalse(action.prepareSaveInvoked); + assertFalse(action.prepareDoSaveInvoked); + assertFalse(action.prepareSubmitInvoked); + assertTrue(action.prepareDoCancelInvoked); + } + + public void testInvokePrefixMethod4() throws Exception { + PrefixMethodInvocationUtilTest.Action1 action = new PrefixMethodInvocationUtilTest.Action1(); + + // ActionProxy + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setReturnValue("noSuchMethod"); + + + // ActionInvocation + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setReturnValue(action); + mockActionInvocation.getProxy(); + controlActionInvocation.setReturnValue(mockActionProxy); + + controlActionProxy.replay(); + controlActionInvocation.replay(); + + + PrefixMethodInvocationUtil.invokePrefixMethod( + mockActionInvocation, + new String[] { "prepare", "prepareDo" }); + + controlActionProxy.verify(); + controlActionInvocation.verify(); + + assertFalse(action.prepareSaveInvoked); + assertFalse(action.prepareDoSaveInvoked); + assertFalse(action.prepareSubmitInvoked); + assertFalse(action.prepareDoCancelInvoked); + } + + public void testInvokePrefixMethod5() throws Exception { + PrefixMethodInvocationUtilTest.Action1 action = new PrefixMethodInvocationUtilTest.Action1(); + + // ActionProxy + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setReturnValue("save"); + + + // ActionInvocation + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setReturnValue(action); + mockActionInvocation.getProxy(); + controlActionInvocation.setReturnValue(mockActionProxy); + + controlActionProxy.replay(); + controlActionInvocation.replay(); + + + PrefixMethodInvocationUtil.invokePrefixMethod( + mockActionInvocation, + new String[] { "noSuchPrefix", "noSuchPrefixDo" }); + + controlActionProxy.verify(); + controlActionInvocation.verify(); + + assertFalse(action.prepareSaveInvoked); + assertFalse(action.prepareDoSaveInvoked); + assertFalse(action.prepareSubmitInvoked); + assertFalse(action.prepareDoCancelInvoked); + } + + + + + /** + * Just a simple object for testing method invocation on its methods. + * + * @author tm_jee + * @version $Date$ $Id$ + */ + public static class Action1 { + + boolean prepareSaveInvoked = false; + boolean prepareDoSaveInvoked = false; + boolean prepareSubmitInvoked = false; + boolean prepareDoCancelInvoked = false; + + + // save + public void prepareSave() { + prepareSaveInvoked = true; + } + public void prepareDoSave() { + prepareDoSaveInvoked = true; + } + + // submit + public void prepareSubmit() { + prepareSubmitInvoked = true; + } + + // cancel + public void prepareDoCancel() { + prepareDoCancelInvoked = true; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PrepareInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PrepareInterceptorTest.java new file mode 100644 index 000000000..d5ccb85d4 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/PrepareInterceptorTest.java @@ -0,0 +1,176 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.mock.MockActionProxy; +import junit.framework.TestCase; +import org.easymock.MockControl; + +/** + * Unit test for PrepareInterceptor. + * + * @author Claus Ibsen + * @author tm_jee + */ +public class PrepareInterceptorTest extends TestCase { + + private Mock mock; + private PrepareInterceptor interceptor; + + public void testPrepareCalledDefault() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy mockActionProxy = new MockActionProxy(); + mockActionProxy.setMethod("execute"); + mai.setProxy(mockActionProxy); + mai.setAction(mock.proxy()); + mock.expect("prepare"); + + interceptor.intercept(mai); + } + + public void testPrepareCalledFalse() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy mockActionProxy = new MockActionProxy(); + mockActionProxy.setMethod("execute"); + mai.setProxy(mockActionProxy); + mai.setAction(mock.proxy()); + + interceptor.setAlwaysInvokePrepare("false"); + interceptor.intercept(mai); + } + + public void testPrepareCalledTrue() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy mockActionProxy = new MockActionProxy(); + mockActionProxy.setMethod("execute"); + mai.setProxy(mockActionProxy); + mai.setAction(mock.proxy()); + mock.expect("prepare"); + + interceptor.setAlwaysInvokePrepare("true"); + interceptor.intercept(mai); + } + + public void testNoPrepareCalled() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + mai.setAction(new SimpleFooAction()); + + interceptor.doIntercept(mai); + } + + public void testPrefixInvocation1() throws Exception { + + MockControl controlAction = MockControl.createControl(ActionInterface.class); + ActionInterface mockAction = (ActionInterface) controlAction.getMock(); + mockAction.prepareSubmit(); + controlAction.setVoidCallable(1); + mockAction.prepare(); + controlAction.setVoidCallable(1); + + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setDefaultReturnValue("submit"); + + + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setDefaultReturnValue(mockAction); + mockActionInvocation.invoke(); + controlActionInvocation.setDefaultReturnValue("okok"); + mockActionInvocation.getProxy(); + controlActionInvocation.setDefaultReturnValue(mockActionProxy); + + + controlAction.replay(); + controlActionProxy.replay(); + controlActionInvocation.replay(); + + PrepareInterceptor interceptor = new PrepareInterceptor(); + String result = interceptor.intercept(mockActionInvocation); + + assertEquals("okok", result); + + controlAction.verify(); + controlActionProxy.verify(); + controlActionInvocation.verify(); + } + + public void testPrefixInvocation2() throws Exception { + + MockControl controlAction = MockControl.createControl(ActionInterface.class); + ActionInterface mockAction = (ActionInterface) controlAction.getMock(); + mockAction.prepare(); + controlAction.setVoidCallable(1); + + MockControl controlActionProxy = MockControl.createControl(ActionProxy.class); + ActionProxy mockActionProxy = (ActionProxy) controlActionProxy.getMock(); + mockActionProxy.getMethod(); + controlActionProxy.setDefaultReturnValue("save"); + + + MockControl controlActionInvocation = MockControl.createControl(ActionInvocation.class); + ActionInvocation mockActionInvocation = (ActionInvocation) controlActionInvocation.getMock(); + mockActionInvocation.getAction(); + controlActionInvocation.setDefaultReturnValue(mockAction); + mockActionInvocation.invoke(); + controlActionInvocation.setDefaultReturnValue("okok"); + mockActionInvocation.getProxy(); + controlActionInvocation.setDefaultReturnValue(mockActionProxy); + + + controlAction.replay(); + controlActionProxy.replay(); + controlActionInvocation.replay(); + + PrepareInterceptor interceptor = new PrepareInterceptor(); + String result = interceptor.intercept(mockActionInvocation); + + assertEquals("okok", result); + + controlAction.verify(); + controlActionProxy.verify(); + controlActionInvocation.verify(); + } + + + @Override + protected void setUp() throws Exception { + mock = new Mock(Preparable.class); + interceptor = new PrepareInterceptor(); + } + + @Override + protected void tearDown() throws Exception { + mock.verify(); + } + + + /** + * Simple interface to test prefix action invocation + * eg. prepareSubmit(), prepareSave() etc. + * + * @author tm_jee + */ + public interface ActionInterface extends Action, Preparable { + void prepareSubmit(); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptorTest.java new file mode 100644 index 000000000..959a768f6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptorTest.java @@ -0,0 +1,234 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.mock.MockActionProxy; +import com.opensymphony.xwork2.test.Equidae; +import com.opensymphony.xwork2.test.User; + +import java.util.HashMap; +import java.util.Map; + +public class ScopedModelDrivenInterceptorTest extends XWorkTestCase { + + protected ScopedModelDrivenInterceptor inter = null; + + /** + * Set up instance variables required by this test case. + */ + @Override + public void setUp() throws Exception { + super.setUp(); + inter = new ScopedModelDrivenInterceptor(); + inter.setObjectFactory(new ProxyObjectFactory()); + } + + public void testResolveModel() throws Exception { + ActionContext ctx = ActionContext.getContext(); + ctx.setSession(new HashMap()); + + ObjectFactory factory = ObjectFactory.getObjectFactory(); + Object obj = inter.resolveModel(factory, ctx, "java.lang.String", "request", "foo"); + assertNotNull(obj); + assertTrue(obj instanceof String); + assertTrue(obj == ctx.get("foo")); + + obj = inter.resolveModel(factory, ctx, "java.lang.String", "session", "foo"); + assertNotNull(obj); + assertTrue(obj instanceof String); + assertTrue(obj == ctx.getSession().get("foo")); + + obj = inter.resolveModel(factory, ctx, "java.lang.String", "session", "foo"); + assertNotNull(obj); + assertTrue(obj instanceof String); + assertTrue(obj == ctx.getSession().get("foo")); + } + + public void testScopedModelDrivenAction() throws Exception { + inter.setScope("request"); + + ScopedModelDriven action = new MyUserScopedModelDrivenAction(); + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "").build(); + map.setConfig(ac); + mai.setAction(action); + mai.setProxy(map); + + inter.intercept(mai); + inter.destroy(); + + assertNotNull(action.getModel()); + assertNotNull(action.getScopeKey()); + assertEquals("com.opensymphony.xwork2.test.User", action.getScopeKey()); + + Object model = ActionContext.getContext().get(action.getScopeKey()); + assertNotNull(model); + assertTrue("Model should be an User object", model instanceof User); + } + + public void testScopedModelDrivenActionWithSetClassName() throws Exception { + inter.setScope("request"); + inter.setClassName("com.opensymphony.xwork2.test.Equidae"); + inter.setName("queen"); + + ScopedModelDriven action = new MyEquidaeScopedModelDrivenAction(); + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "").build(); + map.setConfig(ac); + mai.setAction(action); + mai.setProxy(map); + + inter.intercept(mai); + inter.destroy(); + + assertNotNull(action.getModel()); + assertNotNull(action.getScopeKey()); + assertEquals("queen", action.getScopeKey()); + + Object model = ActionContext.getContext().get(action.getScopeKey()); + assertNotNull(model); + assertTrue("Model should be an Equidae object", model instanceof Equidae); + } + + public void testModelOnSession() throws Exception { + inter.setScope("session"); + inter.setName("king"); + + User user = new User(); + user.setName("King George"); + Map session = new HashMap(); + ActionContext.getContext().setSession(session); + ActionContext.getContext().getSession().put("king", user); + + ScopedModelDriven action = new MyUserScopedModelDrivenAction(); + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "").build(); + map.setConfig(ac); + mai.setAction(action); + mai.setProxy(map); + + inter.intercept(mai); + inter.destroy(); + + assertNotNull(action.getModel()); + assertNotNull(action.getScopeKey()); + assertEquals("king", action.getScopeKey()); + + Object model = ActionContext.getContext().getSession().get(action.getScopeKey()); + assertNotNull(model); + assertTrue("Model should be an User object", model instanceof User); + assertEquals("King George", ((User) model).getName()); + } + + public void testModelAlreadySetOnAction() throws Exception { + inter.setScope("request"); + inter.setName("king"); + + User user = new User(); + user.setName("King George"); + + ScopedModelDriven action = new MyUserScopedModelDrivenAction(); + action.setModel(user); + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "").build(); + map.setConfig(ac); + mai.setAction(action); + mai.setProxy(map); + + inter.intercept(mai); + inter.destroy(); + + assertNotNull(action.getModel()); + assertNull(action.getScopeKey()); // no scope key as nothing happended + } + + public void testNoScopedModelAction() throws Exception { + Action action = new SimpleAction(); + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "").build(); + map.setConfig(ac); + mai.setAction(action); + mai.setProxy(map); + + inter.intercept(mai); + inter.destroy(); + // nothing happends + } + + private class MyUserScopedModelDrivenAction implements ScopedModelDriven, Action { + + private String key; + private User model; + + public void setModel(Object model) { + this.model = (User) model; + } + + public void setScopeKey(String key) { + this.key = key; + } + + public String getScopeKey() { + return key; + } + + public User getModel() { + return model; + } + + public String execute() throws Exception { + return SUCCESS; + } + + } + + private class MyEquidaeScopedModelDrivenAction implements ScopedModelDriven, Action { + + private String key; + private Equidae model; + + public void setModel(Object model) { + this.model = (Equidae) model; + } + + public void setScopeKey(String key) { + this.key = key; + } + + public String getScopeKey() { + return key; + } + + public Equidae getModel() { + return model; + } + + public String execute() throws Exception { + return SUCCESS; + } + + } + +} + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptorTest.java new file mode 100644 index 000000000..8b043adef --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptorTest.java @@ -0,0 +1,209 @@ +/* + * 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.interceptor; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.SimpleFooAction; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.Parameterizable; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.mock.MockActionProxy; + +import java.util.Map; +import java.util.HashMap; + +/** + * Unit test of {@link StaticParametersInterceptor}. + * + * @author Claus Ibsen + */ +public class StaticParametersInterceptorTest extends XWorkTestCase { + + private StaticParametersInterceptor interceptor; + + public void testParameterizable() throws Exception { + Mock mock = new Mock(Parameterizable.class); + + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "").build(); + + Map params = ac.getParams(); + + map.setConfig(ac); + mai.setProxy(map); + mai.setAction(mock.proxy()); + mock.expect("setParams", params); + + interceptor.intercept(mai); + mock.verify(); + } + + public void testWithOneParameters() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "") + .addParam("top.name", "Santa") + .build(); + + map.setConfig(ac); + mai.setProxy(map); + mai.setAction(new SimpleFooAction()); + + User user = new User(); + ActionContext.getContext().getValueStack().push(user); + int before = ActionContext.getContext().getValueStack().size(); + interceptor.intercept(mai); + + assertEquals(before, ActionContext.getContext().getValueStack().size()); + assertEquals("Santa", user.getName()); + } + + public void testWithOneParametersParse() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "") + .addParam("top.name", "${top.hero}") + .build(); + map.setConfig(ac); + mai.setProxy(map); + mai.setAction(new SimpleFooAction()); + + User user = new User(); + ActionContext.getContext().getValueStack().push(user); + int before = ActionContext.getContext().getValueStack().size(); + interceptor.setParse("true"); + interceptor.intercept(mai); + + assertEquals(before, ActionContext.getContext().getValueStack().size()); + assertEquals("Superman", user.getName()); + } + + public void testWithOneParametersNoParse() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "") + .addParam("top.name", "${top.hero}") + .build(); + map.setConfig(ac); + mai.setProxy(map); + mai.setAction(new SimpleFooAction()); + + User user = new User(); + ActionContext.getContext().getValueStack().push(user); + int before = ActionContext.getContext().getValueStack().size(); + interceptor.setParse("false"); + interceptor.intercept(mai); + + assertEquals(before, ActionContext.getContext().getValueStack().size()); + assertEquals("${top.hero}", user.getName()); + } + + public void testNoMerge() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "") + .addParam("top.name", "${top.hero}") + .build(); + map.setConfig(ac); + mai.setProxy(map); + mai.setAction(new SimpleFooAction()); + + User user = new User(); + ActionContext.getContext().getValueStack().push(user); + ActionContext.getContext().setParameters(new HashMap()); + int before = ActionContext.getContext().getValueStack().size(); + interceptor.setMerge("false"); + interceptor.intercept(mai); + + assertEquals(before, ActionContext.getContext().getValueStack().size()); + assertEquals("${top.hero}", user.getName()); + assertEquals(0, ActionContext.getContext().getParameters().size()); + } + + public void testFewParametersParse() throws Exception { + MockActionInvocation mai = new MockActionInvocation(); + MockActionProxy map = new MockActionProxy(); + ActionConfig ac = new ActionConfig.Builder("", "", "") + .addParam("top.age", "${top.myAge}") + .addParam("top.email", "${top.myEmail}") + .build(); + map.setConfig(ac); + mai.setProxy(map); + mai.setAction(new SimpleFooAction()); + + User user = new User(); + ActionContext.getContext().getValueStack().push(user); + int before = ActionContext.getContext().getValueStack().size(); + interceptor.setParse("true"); + interceptor.intercept(mai); + + assertEquals(before, ActionContext.getContext().getValueStack().size()); + assertEquals(user.getMyAge(), user.age); + assertEquals(user.getMyEmail(), user.email); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + interceptor = new StaticParametersInterceptor(); + interceptor.init(); + container.inject(interceptor); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + interceptor.destroy(); + } + + private class User { + private String name; + private int age; + private String email; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getMyAge() { + return 33; + } + + public void setAge(int age) { + this.age = age; + } + + public String getMyEmail() { + return "lukasz dot lenart at gmail dot com"; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getHero() { + return "Superman"; + } + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/TimerInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/TimerInterceptorTest.java new file mode 100644 index 000000000..f658536ba --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/TimerInterceptorTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.interceptor; + +import com.opensymphony.xwork2.SimpleFooAction; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.mock.MockActionProxy; +import com.opensymphony.xwork2.util.logging.Logger; + +/** + * Unit test for {@link TimerInterceptor}. + * + * @author Claus Ibsen + */ +public class TimerInterceptorTest extends XWorkTestCase { + + private MyTimerInterceptor interceptor; + private MockActionInvocation mai; + private MockActionProxy ap; + + + public void testTimerInterceptor() throws Exception { + TimerInterceptor real = new TimerInterceptor(); + real.init(); + real.intercept(mai); + real.destroy(); + } + + public void testInvalidLogLevel() throws Exception { + TimerInterceptor real = new TimerInterceptor(); + real.setLogLevel("xxxx"); + real.init(); + try { + real.intercept(mai); + fail("Should not have reached this point."); + } catch (IllegalArgumentException e) { + // success + } + } + + public void testDefault() throws Exception { + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testNoNamespace() throws Exception { + ap.setNamespace(null); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myAction!execute] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testInputMethod() throws Exception { + ap.setMethod("input"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!input] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testTraceLevel() throws Exception { + interceptor.setLogLevel("trace"); + interceptor.intercept(mai); + assertNull(interceptor.message); // no default logging at trace level + assertEquals("trace", interceptor.getLogLevel()); + } + + public void testDebugLevel() throws Exception { + interceptor.setLogLevel("debug"); + interceptor.intercept(mai); + assertNull(interceptor.message); // no default logging at debug level + } + + public void testInfoLevel() throws Exception { + interceptor.setLogLevel("info"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testWarnLevel() throws Exception { + interceptor.setLogLevel("warn"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testErrorLevel() throws Exception { + interceptor.setLogLevel("error"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testFatalLevel() throws Exception { + interceptor.setLogLevel("fatal"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testLogCategory() throws Exception { + interceptor.setLogCategory("com.mycompany.myapp.actiontiming"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertNotSame(interceptor.logger, TimerInterceptor.LOG); + } + + public void testLogCategoryLevel() throws Exception { + interceptor.setLogCategory("com.mycompany.myapp.actiontiming"); + interceptor.setLogLevel("error"); + interceptor.intercept(mai); + assertTrue(interceptor.message.startsWith("Executed action [myApp/myAction!execute] took ")); + assertNotSame(interceptor.logger, TimerInterceptor.LOG); + assertEquals("com.mycompany.myapp.actiontiming", interceptor.getLogCategory()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + interceptor = new MyTimerInterceptor(); + interceptor.init(); + + mai = new MockActionInvocation(); + ap = new MockActionProxy(); + ap.setActionName("myAction"); + ap.setNamespace("myApp"); + ap.setMethod("execute"); + mai.setAction(new SimpleFooAction()); + mai.setProxy(ap); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + interceptor.destroy(); + ap = null; + mai = null; + } + + private class MyTimerInterceptor extends TimerInterceptor { + + private Logger logger; + private String message; + + @Override + protected void doLog(Logger logger, String message) { + super.doLog(logger, message); + + this.logger = logger; + this.message = message; + } + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ValidationInterceptorPrefixMethodInvocationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ValidationInterceptorPrefixMethodInvocationTest.java new file mode 100644 index 000000000..f269d90b5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/ValidationInterceptorPrefixMethodInvocationTest.java @@ -0,0 +1,109 @@ +/* + * 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.interceptor; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.validator.ValidationInterceptor; +import org.easymock.MockControl; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.easymock.IMocksControl; + +import java.util.HashMap; + +/** + * Test ValidationInterceptor's prefix method invocation capabilities. + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ValidationInterceptorPrefixMethodInvocationTest extends XWorkTestCase { + private ActionInvocation invocation; + private ActionConfig config; + private ActionProxy proxy; + private ValidateAction action; + private String result; + private String method; + + public void testPrefixMethodInvocation1() throws Exception { + method = "save"; + result = Action.INPUT; + + ValidationInterceptor interceptor = create(); + String result = interceptor.intercept(invocation); + + assertEquals(Action.INPUT, result); + } + + public void testPrefixMethodInvocation2() throws Exception { + method = "save"; + result = "okok"; + + ValidationInterceptor interceptor = create(); + String result = interceptor.intercept(invocation); + + assertEquals("okok", result); + } + + protected ValidationInterceptor create() { + ObjectFactory objectFactory = container.getInstance(ObjectFactory.class); + return (ValidationInterceptor) objectFactory.buildInterceptor( + new InterceptorConfig.Builder("model", ValidationInterceptor.class.getName()).build(), new HashMap()); + } + + private interface ValidateAction extends Action, Validateable, ValidationAware { + void validateDoSave(); + void validateSubmit(); + String submit(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + config = new ActionConfig.Builder("", "action", "").build(); + invocation = EasyMock.createNiceMock(ActionInvocation.class); + proxy = EasyMock.createNiceMock(ActionProxy.class); + action = EasyMock.createNiceMock(ValidateAction.class); + + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(action).anyTimes(); + EasyMock.expect(invocation.invoke()).andAnswer(new IAnswer() { + public String answer() throws Throwable { + return result; + } + }).anyTimes(); + + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + EasyMock.expect(proxy.getMethod()).andAnswer(new IAnswer() { + public String answer() throws Throwable { + return method; + } + }).anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(action); + EasyMock.replay(proxy); + + ActionContext contex = new ActionContext(new HashMap()); + ActionContext.setContext(contex); + contex.setActionInvocation(invocation); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AllowingByDefaultAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AllowingByDefaultAction.java new file mode 100644 index 000000000..d97c59aea --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AllowingByDefaultAction.java @@ -0,0 +1,23 @@ +package com.opensymphony.xwork2.interceptor.annotations; + +import com.opensymphony.xwork2.ActionSupport; + +/** + * @author martin.gilday + * + */ +public class AllowingByDefaultAction extends ActionSupport { + + @Blocked + private String name; + private String job; + + public void setName(String name) { + this.name = name; + } + + public void setJob(String job) { + this.job = job; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotatedAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotatedAction.java new file mode 100644 index 000000000..6698088d2 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotatedAction.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.interceptor.annotations; + +import com.opensymphony.xwork2.Action; + +/** + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +public class AnnotatedAction extends BaseAnnotatedAction { + + @Before(priority=5) + public String before() { + log = log + "before"; + return null; + } + + public String execute() { + log = log + "-execute"; + return Action.SUCCESS; + } + + @BeforeResult + public void beforeResult() throws Exception { + log = log +"-beforeResult"; + } + + @After(priority=5) + public void after() { + log = log + "-after"; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationParameterFilterUnitTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationParameterFilterUnitTest.java new file mode 100644 index 000000000..593e49e8a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationParameterFilterUnitTest.java @@ -0,0 +1,80 @@ +package com.opensymphony.xwork2.interceptor.annotations; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author martin.gilday + * + */ +public class AnnotationParameterFilterUnitTest extends TestCase { + + /** + * Only "name" should remain in the parameter map. All others + * should be removed + * @throws Exception + */ + public void testBlockingByDefault() throws Exception { + + Map contextMap = new HashMap(); + Map parameterMap = new HashMap(); + + parameterMap.put("job", "Baker"); + parameterMap.put("name", "Martin"); + + contextMap.put(ActionContext.PARAMETERS, parameterMap); + + Mock mockInvocation = new Mock(ActionInvocation.class); + mockInvocation.expectAndReturn("getInvocationContext", new ActionContext(contextMap)); + mockInvocation.matchAndReturn("getAction", new BlockingByDefaultAction()); + mockInvocation.expectAndReturn("invoke", Action.SUCCESS); + + ActionInvocation invocation = (ActionInvocation) mockInvocation.proxy(); + + AnnotationParameterFilterIntereptor intereptor = new AnnotationParameterFilterIntereptor(); + intereptor.intercept(invocation); + + assertEquals("Paramter map should contain one entry", 1, parameterMap.size()); + assertNull(parameterMap.get("job")); + assertNotNull(parameterMap.get("name")); + + } + + /** + * "name" should be removed from the map, as it is blocked. + * All other parameters should remain + * @throws Exception + */ + public void testAllowingByDefault() throws Exception { + + Map contextMap = new HashMap(); + Map parameterMap = new HashMap(); + + parameterMap.put("job", "Baker"); + parameterMap.put("name", "Martin"); + + contextMap.put(ActionContext.PARAMETERS, parameterMap); + + Mock mockInvocation = new Mock(ActionInvocation.class); + mockInvocation.expectAndReturn("getInvocationContext", new ActionContext(contextMap)); + mockInvocation.matchAndReturn("getAction", new AllowingByDefaultAction()); + mockInvocation.expectAndReturn("invoke", Action.SUCCESS); + + ActionInvocation invocation = (ActionInvocation) mockInvocation.proxy(); + + AnnotationParameterFilterIntereptor intereptor = new AnnotationParameterFilterIntereptor(); + intereptor.intercept(invocation); + + assertEquals("Paramter map should contain one entry", 1, parameterMap.size()); + assertNotNull(parameterMap.get("job")); + assertNull(parameterMap.get("name")); + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptorTest.java new file mode 100644 index 000000000..d66495bce --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptorTest.java @@ -0,0 +1,99 @@ +/* + * 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.interceptor.annotations; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.mock.MockResult; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +import java.util.Arrays; + +/** + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +public class AnnotationWorkflowInterceptorTest extends XWorkTestCase { + private static final String ANNOTATED_ACTION = "annotatedAction"; + private static final String SHORTCIRCUITED_ACTION = "shortCircuitedAction"; + private final AnnotationWorkflowInterceptor annotationWorkflow = new AnnotationWorkflowInterceptor(); + + @Override + public void setUp() { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-default.xml"), new MockConfigurationProvider()); + } + + public void testInterceptsBeforeAndAfter() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("", ANNOTATED_ACTION, null); + assertEquals(Action.SUCCESS, proxy.execute()); + AnnotatedAction action = (AnnotatedAction)proxy.getInvocation().getAction(); + assertEquals("baseBefore-before-execute-beforeResult-after", action.log); + } + + public void testInterceptsShortcircuitedAction() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("", SHORTCIRCUITED_ACTION, null); + assertEquals("shortcircuit", proxy.execute()); + ShortcircuitedAction action = (ShortcircuitedAction)proxy.getInvocation().getAction(); + assertEquals("baseBefore-before", action.log); + } + + private class MockConfigurationProvider implements ConfigurationProvider { + private Configuration config; + + public void init(Configuration configuration) throws ConfigurationException { + this.config = configuration; + } + + public boolean needsReload() { + return false; + } + + public void destroy() { } + + + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + if (!builder.contains(ObjectFactory.class)) { + builder.factory(ObjectFactory.class); + } + if (!builder.contains(ActionProxyFactory.class)) { + builder.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class); + } + } + + public void loadPackages() throws ConfigurationException { + PackageConfig packageConfig = new PackageConfig.Builder("default") + .addActionConfig(ANNOTATED_ACTION, new ActionConfig.Builder("defaultPackage", ANNOTATED_ACTION, AnnotatedAction.class.getName()) + .addInterceptors(Arrays.asList(new InterceptorMapping[]{ new InterceptorMapping("annotationWorkflow", annotationWorkflow) })) + .addResultConfig(new ResultConfig.Builder("success", MockResult.class.getName()).build()) + .build()) + .addActionConfig(SHORTCIRCUITED_ACTION, new ActionConfig.Builder("defaultPackage", SHORTCIRCUITED_ACTION, ShortcircuitedAction.class.getName()) + .addInterceptors(Arrays.asList(new InterceptorMapping[]{ new InterceptorMapping("annotationWorkflow", annotationWorkflow) })) + .addResultConfig(new ResultConfig.Builder("shortcircuit", MockResult.class.getName()).build()) + .build()) + .build(); + config.addPackageConfig("defaultPackage", packageConfig); + config.addPackageConfig("default", new PackageConfig.Builder(packageConfig).name("default").build()); + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/BaseAnnotatedAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/BaseAnnotatedAction.java new file mode 100644 index 000000000..ed088f16e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/BaseAnnotatedAction.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.interceptor.annotations; + +/** + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +public class BaseAnnotatedAction { + + protected String log = ""; + + @Before + public String baseBefore() { + log = log + "baseBefore-"; + return null; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/BlockingByDefaultAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/BlockingByDefaultAction.java new file mode 100644 index 000000000..509fb721e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/BlockingByDefaultAction.java @@ -0,0 +1,24 @@ +package com.opensymphony.xwork2.interceptor.annotations; + +import com.opensymphony.xwork2.ActionSupport; + +/** + * @author martin.gilday + * + */ +@BlockByDefault +public class BlockingByDefaultAction extends ActionSupport { + + @Allowed + private String name; + private String job; + + public void setName(String name) { + this.name = name; + } + + public void setJob(String job) { + this.job = job; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/ShortcircuitedAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/ShortcircuitedAction.java new file mode 100644 index 000000000..bfbd48cb6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/interceptor/annotations/ShortcircuitedAction.java @@ -0,0 +1,35 @@ +/* + * 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.interceptor.annotations; + +import com.opensymphony.xwork2.Action; + +/** + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Rainer Hermanns + */ +public class ShortcircuitedAction extends BaseAnnotatedAction { + @Before(priority=5) + public String before() { + log = log + "before"; + return "shortcircuit"; + } + + public String execute() { + log = log + "-execute-"; + return Action.SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java new file mode 100644 index 000000000..362547fd1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java @@ -0,0 +1,722 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.interceptor.ChainingInterceptor; +import com.opensymphony.xwork2.test.User; +import com.opensymphony.xwork2.util.*; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.*; + +import java.lang.reflect.Method; +import java.util.*; + + +/** + * Unit test of {@link ognlUtil}. + * + * @version $Date$ $Id$ + */ +public class OgnlUtilTest extends XWorkTestCase { + + private OgnlUtil ognlUtil; + + @Override + public void setUp() throws Exception { + super.setUp(); + ognlUtil = container.getInstance(OgnlUtil.class); + } + + public void testCanSetADependentObject() throws Exception { + String dogName = "fido"; + + OgnlRuntime.setNullHandler(Owner.class, new NullHandler() { + public Object nullMethodResult(Map map, Object o, String s, Object[] objects) { + return null; + } + + public Object nullPropertyValue(Map map, Object o, Object o1) { + String methodName = o1.toString(); + String getter = "set" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); + Method[] methods = o.getClass().getDeclaredMethods(); + System.out.println(getter); + + for (Method method : methods) { + String name = method.getName(); + + if (!getter.equals(name) || (method.getParameterTypes().length != 1)) { + continue; + } else { + Class clazz = method.getParameterTypes()[0]; + + try { + Object param = clazz.newInstance(); + method.invoke(o, new Object[]{param}); + + return param; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + return null; + } + }); + + Owner owner = new Owner(); + Map context = Ognl.createDefaultContext(owner); + Map props = new HashMap(); + props.put("dog.name", dogName); + + ognlUtil.setProperties(props, owner, context); + assertNotNull("expected Ognl to create an instance of Dog", owner.getDog()); + assertEquals(dogName, owner.getDog().getName()); + } + + public void testCacheEnabled() throws OgnlException { + OgnlUtil.setEnableExpressionCache("true"); + Object expr0 = ognlUtil.compile("test"); + Object expr2 = ognlUtil.compile("test"); + assertSame(expr0, expr2); + } + + public void testCacheDisabled() throws OgnlException { + OgnlUtil.setEnableExpressionCache("false"); + Object expr0 = ognlUtil.compile("test"); + Object expr2 = ognlUtil.compile("test"); + assertNotSame(expr0, expr2); + } + + public void testCanSetDependentObjectArray() { + EmailAction action = new EmailAction(); + Map context = Ognl.createDefaultContext(action); + + Map props = new HashMap(); + props.put("email[0].address", "addr1"); + props.put("email[1].address", "addr2"); + props.put("email[2].address", "addr3"); + + ognlUtil.setProperties(props, action, context); + assertEquals(3, action.email.size()); + assertEquals("addr1", action.email.get(0).toString()); + assertEquals("addr2", action.email.get(1).toString()); + assertEquals("addr3", action.email.get(2).toString()); + } + + public void testCopySameType() { + Foo foo1 = new Foo(); + Foo foo2 = new Foo(); + + Map context = Ognl.createDefaultContext(foo1); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.FEBRUARY); + cal.set(Calendar.DAY_OF_MONTH, 12); + cal.set(Calendar.YEAR, 1982); + + foo1.setTitle("blah"); + foo1.setNumber(1); + foo1.setPoints(new long[]{1, 2, 3}); + foo1.setBirthday(cal.getTime()); + foo1.setUseful(false); + + ognlUtil.copy(foo1, foo2, context); + + assertEquals(foo1.getTitle(), foo2.getTitle()); + assertEquals(foo1.getNumber(), foo2.getNumber()); + assertEquals(foo1.getPoints(), foo2.getPoints()); + assertEquals(foo1.getBirthday(), foo2.getBirthday()); + assertEquals(foo1.isUseful(), foo2.isUseful()); + } + + + public void testIncudeExcludes() { + + Foo foo1 = new Foo(); + Foo foo2 = new Foo(); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.FEBRUARY); + cal.set(Calendar.DAY_OF_MONTH, 12); + cal.set(Calendar.YEAR, 1982); + + foo1.setPoints(new long[]{1, 2, 3}); + foo1.setBirthday(cal.getTime()); + foo1.setUseful(false); + + + foo1.setTitle("foo1 title"); + foo1.setNumber(1); + + foo2.setTitle("foo2 title"); + foo2.setNumber(2); + + Map context = Ognl.createDefaultContext(foo1); + + List excludes = new ArrayList(); + excludes.add("title"); + excludes.add("number"); + + ognlUtil.copy(foo1, foo2, context, excludes, null); + // these values should remain unchanged in foo2 + assertEquals(foo2.getTitle(), "foo2 title"); + assertEquals(foo2.getNumber(), 2); + + // these values should be changed/copied + assertEquals(foo1.getPoints(), foo2.getPoints()); + assertEquals(foo1.getBirthday(), foo2.getBirthday()); + assertEquals(foo1.isUseful(), foo2.isUseful()); + + + Bar b1 = new Bar(); + Bar b2 = new Bar(); + + b1.setTitle("bar1 title"); + b1.setSomethingElse(10); + + + b1.setId(new Long(1)); + + b2.setTitle(""); + b2.setId(new Long(2)); + + context = Ognl.createDefaultContext(b1); + List includes = new ArrayList(); + includes.add("title"); + includes.add("somethingElse"); + + ognlUtil.copy(b1, b2, context, null, includes); + // includes properties got copied + assertEquals(b1.getTitle(), b2.getTitle()); + assertEquals(b1.getSomethingElse(), b2.getSomethingElse()); + + // id properties did not + assertEquals(b2.getId(), new Long(2)); + + } + + + public void testCopyUnevenObjects() { + Foo foo = new Foo(); + Bar bar = new Bar(); + + Map context = Ognl.createDefaultContext(foo); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.FEBRUARY); + cal.set(Calendar.DAY_OF_MONTH, 12); + cal.set(Calendar.YEAR, 1982); + + foo.setTitle("blah"); + foo.setNumber(1); + foo.setPoints(new long[]{1, 2, 3}); + foo.setBirthday(cal.getTime()); + foo.setUseful(false); + + ognlUtil.copy(foo, bar, context); + + assertEquals(foo.getTitle(), bar.getTitle()); + assertEquals(0, bar.getSomethingElse()); + } + + public void testDeepSetting() { + Foo foo = new Foo(); + foo.setBar(new Bar()); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("bar.title", "i am barbaz"); + ognlUtil.setProperties(props, foo, context); + + assertEquals(foo.getBar().getTitle(), "i am barbaz"); + } + + public void testNoExceptionForUnmatchedGetterAndSetterWithThrowPropertyException() { + Map props = new HashMap(); + props.put("myIntegerProperty", new Integer(1234)); + + TestObject testObject = new TestObject(); + + //this used to fail in OGNL versions < 2.7 + ognlUtil.setProperties(props, testObject, true); + assertEquals(1234, props.get("myIntegerProperty")); + } + + public void testExceptionForWrongPropertyNameWithThrowPropertyException() { + Map props = new HashMap(); + props.put("myStringProperty", "testString"); + + TestObject testObject = new TestObject(); + + try { + ognlUtil.setProperties(props, testObject, true); + fail("Should rise NoSuchPropertyException because of wrong property name"); + } catch (Exception e) { + //expected + } + } + + public void testOgnlHandlesCrapAtTheEndOfANumber() { + Foo foo = new Foo(); + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("aLong", "123a"); + + ognlUtil.setProperties(props, foo, context); + assertEquals(0, foo.getALong()); + } + + /** + * Test that type conversion is performed on indexed collection properties. + */ + public void testSetIndexedValue() { + ValueStack stack = ActionContext.getContext().getValueStack(); + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + User user = new User(); + stack.push(user); + + // indexed string w/ existing array + user.setList(new ArrayList()); + user.getList().add(""); + + String[] foo = new String[]{"asdf"}; + stack.setValue("list[0]", foo); + assertNotNull(user.getList()); + assertEquals(1, user.getList().size()); + assertEquals(String.class, user.getList().get(0).getClass()); + assertEquals("asdf", user.getList().get(0)); + } + + public void testSetPropertiesBoolean() { + Foo foo = new Foo(); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("useful", "true"); + ognlUtil.setProperties(props, foo, context); + + assertEquals(true, foo.isUseful()); + + props = new HashMap(); + props.put("useful", "false"); + ognlUtil.setProperties(props, foo, context); + + assertEquals(false, foo.isUseful()); + } + + public void testSetPropertiesDate() { + Locale orig = Locale.getDefault(); + Locale.setDefault(Locale.US); + + Foo foo = new Foo(); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("birthday", "02/12/1982"); + // US style test + ognlUtil.setProperties(props, foo, context); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.FEBRUARY); + cal.set(Calendar.DAY_OF_MONTH, 12); + cal.set(Calendar.YEAR, 1982); + + assertEquals(cal.getTime(), foo.getBirthday()); + + Locale.setDefault(Locale.UK); + //UK style test + props.put("event", "18/10/2006 14:23:45"); + props.put("meeting", "09/09/2006 14:30"); + ognlUtil.setProperties(props, foo, context); + + cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.OCTOBER); + cal.set(Calendar.DAY_OF_MONTH, 18); + cal.set(Calendar.YEAR, 2006); + cal.set(Calendar.HOUR_OF_DAY, 14); + cal.set(Calendar.MINUTE, 23); + cal.set(Calendar.SECOND, 45); + + assertEquals(cal.getTime(), foo.getEvent()); + + cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.SEPTEMBER); + cal.set(Calendar.DAY_OF_MONTH, 9); + cal.set(Calendar.YEAR, 2006); + cal.set(Calendar.HOUR_OF_DAY, 14); + cal.set(Calendar.MINUTE, 30); + + assertEquals(cal.getTime(), foo.getMeeting()); + + Locale.setDefault(orig); + + Locale.setDefault(orig); + + //test RFC 3339 date format for JSON + props.put("event", "1996-12-19T16:39:57Z"); + ognlUtil.setProperties(props, foo, context); + + cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.MONTH, Calendar.DECEMBER); + cal.set(Calendar.DAY_OF_MONTH, 19); + cal.set(Calendar.YEAR, 1996); + cal.set(Calendar.HOUR_OF_DAY, 16); + cal.set(Calendar.MINUTE, 39); + cal.set(Calendar.SECOND, 57); + + assertEquals(cal.getTime(), foo.getEvent()); + + //test setting a calendar property + props.put("calendar", "1996-12-19T16:39:57Z"); + ognlUtil.setProperties(props, foo, context); + assertEquals(cal, foo.getCalendar()); + } + + public void testSetPropertiesInt() { + Foo foo = new Foo(); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("number", "2"); + ognlUtil.setProperties(props, foo, context); + + assertEquals(2, foo.getNumber()); + } + + public void testSetPropertiesLongArray() { + Foo foo = new Foo(); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("points", new String[]{"1", "2"}); + ognlUtil.setProperties(props, foo, context); + + assertNotNull(foo.getPoints()); + assertEquals(2, foo.getPoints().length); + assertEquals(1, foo.getPoints()[0]); + assertEquals(2, foo.getPoints()[1]); + } + + public void testSetPropertiesString() { + Foo foo = new Foo(); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("title", "this is a title"); + ognlUtil.setProperties(props, foo, context); + + assertEquals(foo.getTitle(), "this is a title"); + } + + public void testSetProperty() { + Foo foo = new Foo(); + Map context = Ognl.createDefaultContext(foo); + assertFalse(123456 == foo.getNumber()); + ognlUtil.setProperty("number", "123456", foo, context); + assertEquals(123456, foo.getNumber()); + } + + + public void testSetList() throws Exception { + ChainingInterceptor foo = new ChainingInterceptor(); + ChainingInterceptor foo2 = new ChainingInterceptor(); + + OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null); + SimpleNode expression = (SimpleNode) Ognl.parseExpression("{'a','ruby','b','tom'}"); + + + Ognl.getValue(expression, context, "aksdj"); + + final ValueStack stack = ActionContext.getContext().getValueStack(); + + Object result = Ognl.getValue(ognlUtil.compile("{\"foo\",'ruby','b','tom'}"), context, foo); + foo.setIncludes((Collection) result); + + assertEquals(4, foo.getIncludes().size()); + assertEquals("foo", foo.getIncludes().toArray()[0]); + assertEquals("ruby", foo.getIncludes().toArray()[1]); + assertEquals("b", "" + foo.getIncludes().toArray()[2]); + assertEquals("tom", foo.getIncludes().toArray()[3]); + + Object result2 = Ognl.getValue(ognlUtil.compile("{\"foo\",'ruby','b','tom'}"), context, foo2); + ognlUtil.setProperty("includes", result2, foo2, context); + + assertEquals(4, foo.getIncludes().size()); + assertEquals("foo", foo.getIncludes().toArray()[0]); + assertEquals("ruby", foo.getIncludes().toArray()[1]); + assertEquals("b", "" + foo.getIncludes().toArray()[2]); + assertEquals("tom", foo.getIncludes().toArray()[3]); + + result = ActionContext.getContext().getValueStack().findValue("{\"foo\",'ruby','b','tom'}"); + + foo.setIncludes((Collection) result); + assertEquals(ArrayList.class, result.getClass()); + + assertEquals(4, foo.getIncludes().size()); + assertEquals("foo", foo.getIncludes().toArray()[0]); + assertEquals("ruby", foo.getIncludes().toArray()[1]); + assertEquals("b", "" + foo.getIncludes().toArray()[2]); + assertEquals("tom", foo.getIncludes().toArray()[3]); + } + + + public void testStringToLong() { + Foo foo = new Foo(); + + Map context = Ognl.createDefaultContext(foo); + + Map props = new HashMap(); + props.put("aLong", "123"); + + ognlUtil.setProperties(props, foo, context); + assertEquals(123, foo.getALong()); + + props.put("aLong", new String[]{"123"}); + + foo.setALong(0); + ognlUtil.setProperties(props, foo, context); + assertEquals(123, foo.getALong()); + } + + public void testNullProperties() { + Foo foo = new Foo(); + foo.setALong(88); + + Map context = Ognl.createDefaultContext(foo); + + ognlUtil.setProperties(null, foo, context); + assertEquals(88, foo.getALong()); + + Map props = new HashMap(); + props.put("aLong", "99"); + ognlUtil.setProperties(props, foo, context); + assertEquals(99, foo.getALong()); + } + + public void testCopyNull() { + Foo foo = new Foo(); + Map context = Ognl.createDefaultContext(foo); + ognlUtil.copy(null, null, context); + + ognlUtil.copy(foo, null, context); + ognlUtil.copy(null, foo, context); + } + + public void testGetTopTarget() throws Exception { + Foo foo = new Foo(); + Map context = Ognl.createDefaultContext(foo); + + CompoundRoot root = new CompoundRoot(); + Object top = ognlUtil.getRealTarget("top", context, root); + assertEquals(root, top); // top should be root + + root.push(foo); + Object val = ognlUtil.getRealTarget("unknown", context, root); + assertNull(val); // not found + } + + public void testGetBeanMap() throws Exception { + Bar bar = new Bar(); + bar.setTitle("I have beer"); + + Foo foo = new Foo(); + foo.setALong(123); + foo.setNumber(44); + foo.setBar(bar); + foo.setTitle("Hello Santa"); + foo.setUseful(true); + + // just do some of the 15 tests + Map beans = ognlUtil.getBeanMap(foo); + assertNotNull(beans); + assertEquals(19, beans.size()); + assertEquals("Hello Santa", beans.get("title")); + assertEquals(new Long("123"), beans.get("ALong")); + assertEquals(new Integer("44"), beans.get("number")); + assertEquals(bar, beans.get("bar")); + assertEquals(Boolean.TRUE, beans.get("useful")); + } + + public void testGetBeanMapNoReadMethod() throws Exception { + MyWriteBar bar = new MyWriteBar(); + bar.setBar("Sams"); + + Map beans = ognlUtil.getBeanMap(bar); + assertEquals(2, beans.size()); + assertEquals(new Integer("1"), beans.get("id")); + assertEquals("There is no read method for bar", beans.get("bar")); + } + + /** + * XW-281 + */ + public void testSetBigIndexedValue() { + ValueStack stack = ActionContext.getContext().getValueStack(); + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.FALSE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + User user = new User(); + stack.push(user); + + // indexed string w/ existing array + user.setList(new ArrayList()); + + String[] foo = new String[]{"asdf"}; + ((OgnlValueStack)stack).setDevMode("true"); + try { + stack.setValue("list.1114778947765", foo); + fail("non-valid expression: list.1114778947765"); + } + catch(RuntimeException ex) { + ; // it's oke + } + + try { + stack.setValue("1114778947765", foo); + fail("non-valid expression: 1114778947765"); + } + catch(RuntimeException ex) { + ; + } + + try { + stack.setValue("1234", foo); + fail("non-valid expression: 1114778947765"); + } + catch(RuntimeException ex) { + ; + } + + ((OgnlValueStack)stack).setDevMode("false"); + stack.setValue("list.1114778947765", foo); + stack.setValue("1114778947765", foo); + stack.setValue("1234", foo); + } + + + public static class Email { + String address; + + public void setAddress(String address) { + this.address = address; + } + + @Override + public String toString() { + return address; + } + } + + static class TestObject { + private Integer myIntegerProperty; + private Long myLongProperty; + private String myStrProperty; + + public void setMyIntegerProperty(Integer myIntegerProperty) { + this.myIntegerProperty = myIntegerProperty; + } + + public String getMyIntegerProperty() { + return myIntegerProperty.toString(); + } + + public void setMyLongProperty(Long myLongProperty) { + this.myLongProperty = myLongProperty; + } + + public Long getMyLongProperty() { + return myLongProperty; + } + + public void setMyStrProperty(String myStrProperty) { + this.myStrProperty = myStrProperty; + } + + public String getMyStrProperty() { + return myStrProperty; + } + } + + class EmailAction { + public List email = new OgnlList(Email.class); + + public List getEmail() { + return this.email; + } + } + + class OgnlList extends ArrayList { + private Class clazz; + + public OgnlList(Class clazz) { + this.clazz = clazz; + } + + @Override + public synchronized Object get(int index) { + while (index >= this.size()) { + try { + this.add(clazz.newInstance()); + } catch (Exception e) { + throw new XWorkException(e); + } + } + + return super.get(index); + } + } + + private class MyWriteBar { + private int id; + + public int getId() { + return id; + } + + public void setBar(String name) { + if ("Sams".equals(name)) + id = 1; + else + id = 999; + } + + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java new file mode 100644 index 000000000..547a4ea4b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java @@ -0,0 +1,1027 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.test.TestBean2; +import com.opensymphony.xwork2.util.*; +import com.opensymphony.xwork2.util.Foo; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.PropertyAccessor; + +import java.io.*; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Unit test for OgnlValueStack. + */ +public class OgnlValueStackTest extends XWorkTestCase { + + public static Integer staticNullMethod() { + return null; + } + + private OgnlUtil ognlUtil; + + @Override + public void setUp() throws Exception { + super.setUp(); + ognlUtil = container.getInstance(OgnlUtil.class); + } + + private OgnlValueStack createValueStack() { + return createValueStack(true); + } + + private OgnlValueStack createValueStack(boolean allowStaticMethodAccess) { + OgnlValueStack stack = new OgnlValueStack( + container.getInstance(XWorkConverter.class), + (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), + container.getInstance(TextProvider.class, "system"), allowStaticMethodAccess); + container.inject(stack); + return stack; + } + + public void testExpOverridesCanStackExpUp() throws Exception { + Map expr1 = new LinkedHashMap(); + expr1.put("expr1", "'expr1value'"); + + OgnlValueStack vs = createValueStack(); + vs.setExprOverrides(expr1); + + assertEquals(vs.findValue("expr1"), "expr1value"); + + Map expr2 = new LinkedHashMap(); + expr2.put("expr2", "'expr2value'"); + expr2.put("expr3", "'expr3value'"); + vs.setExprOverrides(expr2); + + assertEquals(vs.findValue("expr2"), "expr2value"); + assertEquals(vs.findValue("expr3"), "expr3value"); + } + + + public void testArrayAsString() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setAge(12); + dog.setName("Rover"); + dog.setChildAges(new int[]{1, 2}); + + vs.push(dog); + assertEquals("1, 2", vs.findValue("childAges", String.class)); + } + + public void testFailOnException() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + vs.push(dog); + try { + vs.findValue("bite", true); + fail("Failed to throw exception on EL error"); + } catch (Exception ex) { + //ok + } + } + + public void testFailOnErrorOnInheritedProperties() { + //this shuld not fail as the property is defined on a parent class + OgnlValueStack vs = createValueStack(); + + Foo foo = new Foo(); + BarJunior barjr = new BarJunior(); + foo.setBarJunior(barjr); + vs.push(foo); + + assertNull(barjr.getTitle()); + vs.findValue("barJunior.title", true); + } + + public void testSuccessFailOnErrorOnInheritedPropertiesWithMethods() { + //this shuld not fail as the property is defined on a parent class + OgnlValueStack vs = createValueStack(); + + Foo foo = new Foo(); + BarJunior barjr = new BarJunior(); + foo.setBarJunior(barjr); + vs.push(foo); + + assertNull(barjr.getTitle()); + vs.findValue("getBarJunior().title", true); + } + + public void testFailFailOnErrorOnInheritedPropertiesWithMethods() { + OgnlValueStack vs = createValueStack(); + + Foo foo = new Foo(); + BarJunior barjr = new BarJunior(); + foo.setBarJunior(barjr); + vs.push(foo); + + assertNull(barjr.getTitle()); + try { + vs.findValue("getBarJunior().title2", true); + fail("should have failed on missing property"); + } catch (Exception e) { + } + } + + public void testFailOnMissingProperty() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + vs.push(dog); + try { + vs.findValue("someprop", true); + fail("Failed to throw exception on EL missing property"); + } catch (Exception ex) { + //ok + } + } + + public void testFailOnMissingMethod() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + vs.push(dog); + try { + vs.findValue("someprop()", true); + fail("Failed to throw exception on EL missing method"); + } catch (Exception ex) { + //ok + } + } + + public void testFailsOnMethodThatThrowsException() { + SimpleAction action = new SimpleAction(); + OgnlValueStack stack = createValueStack(); + stack.push(action); + + action.setThrowException(true); + try { + stack.findValue("exceptionMethod1()", true); + fail("Failed to throw exception on EL method exception"); + } catch (Exception ex) { + //ok + } + } + + + public void testDoesNotFailOnNonActionObjects() { + //if a value is not found, then it will check for missing properties + //it needs to check in all objects in the stack, not only actions, see WW-3306 + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setHates(null); + vs.push(dog); + vs.findValue("hates", true); + } + + + public void testFailOnMissingNestedProperty() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setHates(new Cat()); + vs.push(dog); + try { + vs.findValue("hates.someprop", true); + fail("Failed to throw exception on EL missing nested property"); + } catch (Exception ex) { + //ok + } + } + + public void testBasic() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setAge(12); + dog.setName("Rover"); + + vs.push(dog); + assertEquals("Rover", vs.findValue("name", String.class)); + } + + public void testStatic() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setDeity("fido"); + vs.push(dog); + assertEquals("fido", vs.findValue("@com.opensymphony.xwork2.util.Dog@getDeity()", String.class)); + } + + public void testStaticMethodDisallow() { + OgnlValueStack vs = createValueStack(false); + + Dog dog = new Dog(); + dog.setDeity("fido"); + vs.push(dog); + assertNull(vs.findValue("@com.opensymphony.xwork2.util.Dog@getDeity()", String.class)); + } + + public void testBasicSet() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setAge(12); + dog.setName("Rover"); + + vs.set("dog", dog); + assertEquals("Rover", vs.findValue("dog.name", String.class)); + } + + public void testCallMethodOnNullObject() { + OgnlValueStack stack = createValueStack(); + assertNull(stack.findValue("foo.size()")); + } + + public void testCallMethodThatThrowsExceptionTwice() { + SimpleAction action = new SimpleAction(); + OgnlValueStack stack = createValueStack(); + stack.push(action); + + action.setThrowException(true); + assertNull(stack.findValue("exceptionMethod1()")); + action.setThrowException(false); + assertEquals("OK", stack.findValue("exceptionMethod()")); + } + + + public void testCallMethodWithNullArg() { + SimpleAction action = new SimpleAction(); + OgnlValueStack stack = createValueStack(); + stack.push(action); + + stack.findValue("setName(blah)"); + assertNull(action.getName()); + + action.setBlah("blah"); + stack.findValue("setName(blah)"); + assertEquals("blah", action.getName()); + } + + public void testConvertStringArrayToList() { + Foo foo = new Foo(); + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + vs.setValue("strings", new String[]{"one", "two"}); + + assertNotNull(foo.getStrings()); + assertEquals("one", foo.getStrings().get(0)); + assertEquals("two", foo.getStrings().get(1)); + } + + public void testFindValueWithConversion() { + + // register converter + TestBean2 tb2 = new TestBean2(); + + OgnlValueStack stack = createValueStack(); + stack.push(tb2); + Map myContext = stack.getContext(); + + Map props = new HashMap(); + props.put("cat", "Kitty"); + ognlUtil.setProperties(props, tb2, myContext); + // expect String to be converted into a Cat + assertEquals("Kitty", tb2.getCat().getName()); + + // findValue should be able to access the name + Object value = stack.findValue("cat.name == 'Kitty'", Boolean.class); + assertNotNull(value); + assertEquals(Boolean.class, value.getClass()); + assertEquals(Boolean.TRUE, value); + + value = stack.findValue("cat == null", Boolean.class); + assertNotNull(value); + assertEquals(Boolean.class, value.getClass()); + assertEquals(Boolean.FALSE, value); + } + + + public void testDeepProperties() { + OgnlValueStack vs = createValueStack(); + + Cat cat = new Cat(); + cat.setName("Smokey"); + + Dog dog = new Dog(); + dog.setAge(12); + dog.setName("Rover"); + dog.setChildAges(new int[]{1, 2}); + dog.setHates(cat); + + vs.push(dog); + assertEquals("Smokey", vs.findValue("hates.name", String.class)); + } + + public void testFooBarAsString() { + OgnlValueStack vs = createValueStack(); + Foo foo = new Foo(); + Bar bar = new Bar(); + bar.setTitle("blah"); + bar.setSomethingElse(123); + foo.setBar(bar); + + vs.push(foo); + assertEquals("blah:123", vs.findValue("bar", String.class)); + } + + public void testGetBarAsString() { + Foo foo = new Foo(); + Bar bar = new Bar(); + bar.setTitle("bar"); + bar.setSomethingElse(123); + foo.setBar(bar); + + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + String output = (String) vs.findValue("bar", String.class); + assertEquals("bar:123", output); + } + + public void testGetComplexBarAsString() { + // children foo->foo->foo + Foo foo = new Foo(); + Foo foo2 = new Foo(); + foo.setChild(foo2); + + Foo foo3 = new Foo(); + foo2.setChild(foo3); + + // relatives + Foo fooA = new Foo(); + foo.setRelatives(new Foo[]{fooA}); + + Foo fooB = new Foo(); + foo2.setRelatives(new Foo[]{fooB}); + + Foo fooC = new Foo(); + foo3.setRelatives(new Foo[]{fooC}); + + // the bar + Bar bar = new Bar(); + bar.setTitle("bar"); + bar.setSomethingElse(123); + + // now place the bar all over + foo.setBar(bar); + foo2.setBar(bar); + foo3.setBar(bar); + fooA.setBar(bar); + fooB.setBar(bar); + fooC.setBar(bar); + + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + vs.getContext().put("foo", foo); + + assertEquals("bar:123", vs.findValue("#foo.bar", String.class)); + assertEquals("bar:123", vs.findValue("bar", String.class)); + assertEquals("bar:123", vs.findValue("child.bar", String.class)); + assertEquals("bar:123", vs.findValue("child.child.bar", String.class)); + assertEquals("bar:123", vs.findValue("relatives[0].bar", String.class)); + assertEquals("bar:123", vs.findValue("child.relatives[0].bar", String.class)); + assertEquals("bar:123", vs.findValue("child.child.relatives[0].bar", String.class)); + + vs.push(vs.findValue("child")); + assertEquals("bar:123", vs.findValue("bar", String.class)); + assertEquals("bar:123", vs.findValue("child.bar", String.class)); + assertEquals("bar:123", vs.findValue("relatives[0].bar", String.class)); + assertEquals("bar:123", vs.findValue("child.relatives[0].bar", String.class)); + } + + public void testGetNullValue() { + Dog dog = new Dog(); + OgnlValueStack stack = createValueStack(); + stack.push(dog); + assertNull(stack.findValue("name")); + } + + public void testMapEntriesAvailableByKey() { + Foo foo = new Foo(); + String title = "a title"; + foo.setTitle(title); + + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + Map map = new HashMap(); + String a_key = "a"; + String a_value = "A"; + map.put(a_key, a_value); + + String b_key = "b"; + String b_value = "B"; + map.put(b_key, b_value); + + vs.push(map); + + assertEquals(title, vs.findValue("title")); + assertEquals(a_value, vs.findValue(a_key)); + assertEquals(b_value, vs.findValue(b_key)); + } + + public void testMethodCalls() { + OgnlValueStack vs = createValueStack(); + + Dog dog1 = new Dog(); + dog1.setAge(12); + dog1.setName("Rover"); + + Dog dog2 = new Dog(); + dog2.setAge(1); + dog2.setName("Jack"); + vs.push(dog1); + vs.push(dog2); + + //assertEquals(new Boolean(false), vs.findValue("'Rover'.endsWith('Jack')")); + //assertEquals(new Boolean(false), vs.findValue("'Rover'.endsWith(name)")); + //assertEquals("RoverJack", vs.findValue("[1].name + name")); + assertEquals(new Boolean(false), vs.findValue("[1].name.endsWith(name)")); + + assertEquals(new Integer(1 * 7), vs.findValue("computeDogYears()")); + assertEquals(new Integer(1 * 2), vs.findValue("multiplyAge(2)")); + assertEquals(new Integer(12 * 7), vs.findValue("[1].computeDogYears()")); + assertEquals(new Integer(12 * 5), vs.findValue("[1].multiplyAge(5)")); + assertNull(vs.findValue("thisMethodIsBunk()")); + assertEquals(new Integer(12 * 1), vs.findValue("[1].multiplyAge(age)")); + + assertEquals("Jack", vs.findValue("name")); + assertEquals("Rover", vs.findValue("[1].name")); + + //hates will be null + assertEquals(Boolean.TRUE, vs.findValue("nullSafeMethod(hates)")); + } + + public void testMismatchedGettersAndSettersCauseExceptionInSet() { + OgnlValueStack vs = createValueStack(); + + BadJavaBean bean = new BadJavaBean(); + vs.push(bean); + + //this used to fail in OGNl versdion < 2.7 + vs.setValue("count", "1", true); + assertEquals("1", bean.getCount()); + + try { + vs.setValue("count2", "a", true); + fail("Expected an exception for mismatched getter and setter"); + } catch (XWorkException e) { + //expected + } + } + + public void testNoExceptionInSetForDefault() { + OgnlValueStack vs = createValueStack(); + + BadJavaBean bean = new BadJavaBean(); + vs.push(bean); + + //this used to fail in OGNl versdion < 2.7 + vs.setValue("count", "1", true); + assertEquals("1", bean.getCount()); + + try { + vs.setValue("count2", "a", true); + fail("Expected an exception for mismatched getter and setter"); + } catch (XWorkException e) { + //expected + } + } + + public void testNullEntry() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setName("Rover"); + + vs.push(dog); + assertEquals("Rover", vs.findValue("name", String.class)); + + vs.push(null); + assertEquals("Rover", vs.findValue("name", String.class)); + } + + public void testNullMethod() { + Dog dog = new Dog(); + OgnlValueStack stack = createValueStack(); + stack.push(dog); + assertNull(stack.findValue("nullMethod()")); + assertNull(stack.findValue("@com.opensymphony.xwork2.util.OgnlValueStackTest@staticNullMethod()")); + } + + public void testPetSoarBug() { + Cat cat = new Cat(); + cat.setFoo(new Foo()); + + Bar bar = new Bar(); + bar.setTitle("bar"); + bar.setSomethingElse(123); + cat.getFoo().setBar(bar); + + OgnlValueStack vs = createValueStack(); + vs.push(cat); + + assertEquals("bar:123", vs.findValue("foo.bar", String.class)); + } + + public void testPrimitiveSettingWithInvalidValueAddsFieldErrorInDevMode() { + SimpleAction action = new SimpleAction(); + OgnlValueStack stack = createValueStack(); + stack.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + stack.setDevMode("true"); + stack.push(action); + + try { + stack.setValue("bar", "3x"); + fail("Attempt to set 'bar' int property to '3x' should result in RuntimeException"); + } + catch (RuntimeException re) { + assertTrue(true); + } + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertTrue(conversionErrors.containsKey("bar")); + } + + public void testPrimitiveSettingWithInvalidValueAddsFieldErrorInNonDevMode() { + SimpleAction action = new SimpleAction(); + OgnlValueStack stack = createValueStack(); + stack.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + stack.setDevMode("false"); + stack.push(action); + stack.setValue("bar", "3x"); + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertTrue(conversionErrors.containsKey("bar")); + } + + + public void testObjectSettingWithInvalidValueDoesNotCauseSetCalledWithNull() { + SimpleAction action = new SimpleAction(); + action.setBean(new TestBean()); + OgnlValueStack stack = createValueStack(); + stack.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + stack.push(action); + try { + stack.setValue("bean", "foobar", true); + fail("Should have thrown a type conversion exception"); + } catch (XWorkException e) { + // expected + } + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertTrue(conversionErrors.containsKey("bean")); + assertNotNull(action.getBean()); + } + + + public void testSerializable() throws IOException, ClassNotFoundException { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + dog.setAge(12); + dog.setName("Rover"); + + vs.push(dog); + assertEquals("Rover", vs.findValue("name", String.class)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + + oos.writeObject(vs); + oos.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + + OgnlValueStack newVs = (OgnlValueStack) ois.readObject(); + assertEquals("Rover", newVs.findValue("name", String.class)); + } + + public void testSetAfterPush() { + OgnlValueStack vs = createValueStack(); + + Dog d = new Dog(); + d.setName("Rover"); + vs.push(d); + + vs.set("name", "Bill"); + + assertEquals("Bill", vs.findValue("name")); + + } + + public void testSetBarAsString() { + Foo foo = new Foo(); + + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + vs.setValue("bar", "bar:123"); + + assertEquals("bar", foo.getBar().getTitle()); + assertEquals(123, foo.getBar().getSomethingElse()); + } + + public void testSetBeforePush() { + OgnlValueStack vs = createValueStack(); + + vs.set("name", "Bill"); + Dog d = new Dog(); + d.setName("Rover"); + vs.push(d); + + assertEquals("Rover", vs.findValue("name")); + + } + + public void testSetDeepBarAsString() { + Foo foo = new Foo(); + Foo foo2 = new Foo(); + foo.setChild(foo2); + + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + vs.setValue("child.bar", "bar:123"); + + assertEquals("bar", foo.getChild().getBar().getTitle()); + assertEquals(123, foo.getChild().getBar().getSomethingElse()); + } + + public void testSetNullList() { + Foo foo = new Foo(); + OgnlValueStack vs = createValueStack(); + vs.getContext().put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + vs.push(foo); + + vs.setValue("cats[0].name", "Cat One"); + vs.setValue("cats[1].name", "Cat Two"); + + assertNotNull(foo.getCats()); + assertEquals(2, foo.getCats().size()); + assertEquals("Cat One", ((Cat) foo.getCats().get(0)).getName()); + assertEquals("Cat Two", ((Cat) foo.getCats().get(1)).getName()); + + vs.setValue("cats[0].foo.cats[1].name", "Deep null cat"); + assertNotNull(((Cat) foo.getCats().get(0)).getFoo()); + assertNotNull(((Cat) foo.getCats().get(0)).getFoo().getCats()); + assertNotNull(((Cat) foo.getCats().get(0)).getFoo().getCats().get(1)); + assertEquals("Deep null cat", ((Cat) ((Cat) foo.getCats().get(0)).getFoo().getCats().get(1)).getName()); + } + + public void testSetMultiple() { + OgnlValueStack vs = createValueStack(); + int origSize = vs.getRoot().size(); + vs.set("something", new Object()); + vs.set("somethingElse", new Object()); + vs.set("yetSomethingElse", new Object()); + assertEquals(origSize + 1, vs.getRoot().size()); + + } + + public void testSetNullMap() { + Foo foo = new Foo(); + OgnlValueStack vs = createValueStack(); + vs.getContext().put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + vs.push(foo); + + vs.setValue("catMap['One'].name", "Cat One"); + vs.setValue("catMap['Two'].name", "Cat Two"); + + assertNotNull(foo.getCatMap()); + assertEquals(2, foo.getCatMap().size()); + assertEquals("Cat One", ((Cat) foo.getCatMap().get("One")).getName()); + assertEquals("Cat Two", ((Cat) foo.getCatMap().get("Two")).getName()); + + vs.setValue("catMap['One'].foo.catMap['Two'].name", "Deep null cat"); + assertNotNull(((Cat) foo.getCatMap().get("One")).getFoo()); + assertNotNull(((Cat) foo.getCatMap().get("One")).getFoo().getCatMap()); + assertNotNull(((Cat) foo.getCatMap().get("One")).getFoo().getCatMap().get("Two")); + assertEquals("Deep null cat", ((Cat) ((Cat) foo.getCatMap().get("One")).getFoo().getCatMap().get("Two")).getName()); + } + + public void testSetReallyDeepBarAsString() { + Foo foo = new Foo(); + Foo foo2 = new Foo(); + foo.setChild(foo2); + + Foo foo3 = new Foo(); + foo2.setChild(foo3); + + OgnlValueStack vs = createValueStack(); + vs.push(foo); + + vs.setValue("child.child.bar", "bar:123"); + + assertEquals("bar", foo.getChild().getChild().getBar().getTitle()); + assertEquals(123, foo.getChild().getChild().getBar().getSomethingElse()); + } + + public void testSettingDogGender() { + OgnlValueStack vs = createValueStack(); + + Dog dog = new Dog(); + vs.push(dog); + + vs.setValue("male", "false"); + + assertEquals(false, dog.isMale()); + } + + public void testStatics() { + OgnlValueStack vs = createValueStack(); + + Cat cat = new Cat(); + vs.push(cat); + + Dog dog = new Dog(); + dog.setAge(12); + dog.setName("Rover"); + vs.push(dog); + + assertEquals("Canine", vs.findValue("@vs@SCIENTIFIC_NAME")); + assertEquals("Canine", vs.findValue("@vs1@SCIENTIFIC_NAME")); + assertEquals("Feline", vs.findValue("@vs2@SCIENTIFIC_NAME")); + assertEquals(new Integer(BigDecimal.ROUND_HALF_DOWN), vs.findValue("@java.math.BigDecimal@ROUND_HALF_DOWN")); + assertNull(vs.findValue("@vs3@BLAH")); + assertNull(vs.findValue("@com.nothing.here.Nothing@BLAH")); + } + + public void testTop() { + OgnlValueStack vs = createValueStack(); + + Dog dog1 = new Dog(); + dog1.setAge(12); + dog1.setName("Rover"); + + Dog dog2 = new Dog(); + dog2.setAge(1); + dog2.setName("Jack"); + vs.push(dog1); + vs.push(dog2); + + assertEquals(dog2, vs.findValue("top")); + assertEquals("Jack", vs.findValue("top.name")); + } + + public void testTopIsDefaultTextProvider() { + OgnlValueStack vs = createValueStack(); + + assertEquals(container.getInstance(TextProvider.class, "system"), vs.findValue("top")); + } + + public void testTwoDogs() { + OgnlValueStack vs = createValueStack(); + + Dog dog1 = new Dog(); + dog1.setAge(12); + dog1.setName("Rover"); + + Dog dog2 = new Dog(); + dog2.setAge(1); + dog2.setName("Jack"); + vs.push(dog1); + vs.push(dog2); + + assertEquals("Jack", vs.findValue("name")); + assertEquals("Rover", vs.findValue("[1].name")); + + assertEquals(dog2, vs.pop()); + assertEquals("Rover", vs.findValue("name")); + } + + public void testTypeConversionError() { + TestBean bean = new TestBean(); + OgnlValueStack stack = createValueStack(); + stack.push(bean); + stack.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + try { + stack.setValue("count", "a", true); + fail("Should have thrown a type conversion exception"); + } catch (XWorkException e) { + // expected + } + + Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS); + assertTrue(conversionErrors.containsKey("count")); + } + + public void testConstructorWithAStack() { + OgnlValueStack stack = createValueStack(); + stack.push("Hello World"); + + OgnlValueStack stack2 = new OgnlValueStack(stack, + container.getInstance(XWorkConverter.class), + (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), true); + container.inject(stack2); + + assertEquals(stack.getRoot(), stack2.getRoot()); + assertEquals(stack.peek(), stack2.peek()); + assertEquals("Hello World", stack2.pop()); + + } + + public void testDefaultType() { + OgnlValueStack stack = createValueStack(); + stack.setDefaultType(String.class); + stack.push("Hello World"); + + assertEquals("Hello World", stack.findValue("top")); + assertEquals(null, stack.findValue(null)); + + stack.setDefaultType(Integer.class); + stack.push(new Integer(123)); + assertEquals(new Integer(123), stack.findValue("top")); + } + + public void testFindString() { + OgnlValueStack stack = createValueStack(); + stack.setDefaultType(Integer.class); + stack.push("Hello World"); + + assertEquals("Hello World", stack.findString("top")); + assertEquals(null, stack.findString(null)); + } + + public void testExpOverrides() { + Map overrides = new HashMap(); + overrides.put("claus", "top"); + + OgnlValueStack stack = createValueStack(); + stack.setExprOverrides(overrides); + stack.push("Hello World"); + + assertEquals("Hello World", stack.findValue("claus")); + assertEquals("Hello World", stack.findString("claus")); + assertEquals("Hello World", stack.findValue("top")); + assertEquals("Hello World", stack.findString("top")); + + assertEquals("Hello World", stack.findValue("claus", String.class)); + assertEquals("Hello World", stack.findValue("top", String.class)); + + stack.getContext().put("santa", "Hello Santa"); + assertEquals("Hello Santa", stack.findValue("santa", String.class)); + assertEquals(null, stack.findValue("unknown", String.class)); + } + + public void testWarnAboutInvalidProperties() { + OgnlValueStack stack = createValueStack(); + MyAction action = new MyAction(); + action.setName("Don"); + stack.push(action); + + // how to test the warning was logged? + assertEquals("Don", stack.findValue("name", String.class)); + assertEquals(null, stack.findValue("address", String.class)); + // should log warning + assertEquals(null, stack.findValue("address.invalidProperty", String.class)); + + // if country is null, OGNL throws an exception + /*action.setAddress(new Address()); + stack.push(action);*/ + // should log warning + assertEquals(null, stack.findValue("address.country.id", String.class)); + assertEquals(null, stack.findValue("address.country.name", String.class)); + } + + class BadJavaBean { + private int count; + private int count2; + + public void setCount(int count) { + this.count = count; + } + + public String getCount() { + return "" + count; + } + + public void setCount2(String count2) { + this.count2 = Integer.parseInt(count2); + } + + public int getCount2() { + return count2; + } + } + + class MyAction { + private Long id; + private String name; + private Address address; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + class Address { + private String address; + private Country country; + private String city; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + } + + class Country { + private String iso; + private String name; + private String displayName; + + public String getIso() { + return iso; + } + + public void setIso(String iso) { + this.iso = iso; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SetPropertiesTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SetPropertiesTest.java new file mode 100644 index 000000000..271a98d51 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/SetPropertiesTest.java @@ -0,0 +1,334 @@ +/* + * 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. + */ +/* + * Created on 6/10/2003 + * + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.FooBarConverter; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.inject.Context; +import com.opensymphony.xwork2.inject.Factory; +import com.opensymphony.xwork2.inject.Scope; +import com.opensymphony.xwork2.mock.MockObjectTypeDeterminer; +import com.opensymphony.xwork2.test.StubConfigurationProvider; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.util.Cat; +import com.opensymphony.xwork2.util.Foo; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.Ognl; + +import java.util.*; + + +/** + * @author CameronBraid and Gabe + * @author tm_jee + */ +public class SetPropertiesTest extends XWorkTestCase { + + private OgnlUtil ognlUtil; + + @Override + public void setUp() throws Exception { + super.setUp(); + ognlUtil = container.getInstance(OgnlUtil.class); + ((OgnlValueStack)ActionContext.getContext().getValueStack()).setDevMode("true"); + } + public void testOgnlUtilEmptyStringAsLong() { + Bar bar = new Bar(); + Map context = Ognl.createDefaultContext(bar); + context.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + bar.setId(null); + + HashMap props = new HashMap(); + props.put("id", ""); + + ognlUtil.setProperties(props, bar, context); + assertNull(bar.getId()); + assertEquals(0, bar.getFieldErrors().size()); + + props.put("id", new String[]{""}); + + bar.setId(null); + ognlUtil.setProperties(props, bar, context); + assertNull(bar.getId()); + assertEquals(0, bar.getFieldErrors().size()); + } + + public void testSetCollectionByConverterFromArray() { + Foo foo = new Foo(); + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + XWorkConverter c = (XWorkConverter)((OgnlTypeConverterWrapper) Ognl.getTypeConverter(vs.getContext())).getTarget(); + c.registerConverter(Cat.class.getName(), new FooBarConverter()); + vs.push(foo); + + vs.setValue("cats", new String[]{"1", "2"}); + assertNotNull(foo.getCats()); + assertEquals(2, foo.getCats().size()); + assertEquals(Cat.class, foo.getCats().get(0).getClass()); + assertEquals(Cat.class, foo.getCats().get(1).getClass()); + } + + public void testSetCollectionByConverterFromCollection() { + Foo foo = new Foo(); + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + XWorkConverter c = (XWorkConverter)((OgnlTypeConverterWrapper) Ognl.getTypeConverter(vs.getContext())).getTarget(); + c.registerConverter(Cat.class.getName(), new FooBarConverter()); + vs.push(foo); + + HashSet s = new HashSet(); + s.add("1"); + s.add("2"); + vs.setValue("cats", s); + assertNotNull(foo.getCats()); + assertEquals(2, foo.getCats().size()); + assertEquals(Cat.class, foo.getCats().get(0).getClass()); + assertEquals(Cat.class, foo.getCats().get(1).getClass()); + } + + public void testValueStackSetValueEmptyStringAsLong() { + Bar bar = new Bar(); + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + vs.push(bar); + + vs.setValue("id", ""); + assertNull(bar.getId()); + assertEquals(0, bar.getFieldErrors().size()); + + bar.setId(null); + + vs.setValue("id", new String[]{""}); + assertNull(bar.getId()); + assertEquals(0, bar.getFieldErrors().size()); + } + public void testAddingToListsWithObjectsTrue() { + doTestAddingToListsWithObjects(true); + } + public void testAddingToListsWithObjectsFalse() { + doTestAddingToListsWithObjects(false); + + } + public void doTestAddingToListsWithObjects(final boolean allowAdditions) { + + loadConfigurationProviders(new StubConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, + LocatableProperties props) throws ConfigurationException { + builder.factory(ObjectTypeDeterminer.class, new Factory() { + public Object create(Context context) throws Exception { + return new MockObjectTypeDeterminer(null,Cat.class,null,allowAdditions); + } + + }); + } + }); + + Foo foo = new Foo(); + foo.setMoreCats(new ArrayList()); + String spielname = "Spielen"; + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + vs.getContext().put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + vs.push(foo); + try { + vs.setValue("moreCats[2].name", spielname); + } catch (IndexOutOfBoundsException e) { + if (allowAdditions) { + throw e; + } + } + Object setCat = null; + if (allowAdditions) { + setCat = foo.getMoreCats().get(2); + + + assertNotNull(setCat); + assertTrue(setCat instanceof Cat); + assertTrue(((Cat) setCat).getName().equals(spielname)); + } else { + assertTrue(foo.getMoreCats()==null || foo.getMoreCats().size()==0); + } + + //now try to set a lower number + //to test setting after a higher one + //has been created + if (allowAdditions) { + spielname = "paws"; + vs.setValue("moreCats[0].name", spielname); + setCat = foo.getMoreCats().get(0); + assertNotNull(setCat); + assertTrue(setCat instanceof Cat); + assertTrue(((Cat) setCat).getName().equals(spielname)); + } + + } + + + public void testAddingToMapsWithObjectsTrue() throws Exception { + doTestAddingToMapsWithObjects(true); + } + + public void testAddingToMapsWithObjectsFalse() throws Exception { + doTestAddingToMapsWithObjects(false); + + } + + public void doTestAddingToMapsWithObjects(boolean allowAdditions) throws Exception { + + loadButAdd(ObjectTypeDeterminer.class, new MockObjectTypeDeterminer(Long.class,Cat.class,null,allowAdditions)); + + Foo foo = new Foo(); + foo.setAnotherCatMap(new HashMap()); + String spielname = "Spielen"; + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + vs.getContext().put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + vs.push(foo); + vs.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + vs.setValue("anotherCatMap[\"3\"].name", spielname); + Object setCat = foo.getAnotherCatMap().get(new Long(3)); + if (allowAdditions) { + assertNotNull(setCat); + assertTrue(setCat instanceof Cat); + assertTrue(((Cat) setCat).getName().equals(spielname)); + } else { + assertNull(setCat); + } + + + } + + + public void testAddingAndModifyingCollectionWithObjectsSet() { + doTestAddingAndModifyingCollectionWithObjects(new HashSet()); + } + public void testAddingAndModifyingCollectionWithObjectsList() { + doTestAddingAndModifyingCollectionWithObjects(new ArrayList()); + + } + public void doTestAddingAndModifyingCollectionWithObjects(Collection barColl) { + + ValueStack vs = ActionContext.getContext().getValueStack(); + Foo foo = new Foo(); + + foo.setBarCollection(barColl); + Bar bar1 = new Bar(); + bar1.setId(new Long(11)); + barColl.add(bar1); + Bar bar2 = new Bar(); + bar2.setId(new Long(22)); + barColl.add(bar2); + //try modifying bar1 and bar2 + //check the logs here to make sure + //the Map is being created + ReflectionContextState.setCreatingNullObjects(vs.getContext(), true); + ReflectionContextState.setReportingConversionErrors(vs.getContext(), true); + vs.push(foo); + String bar1Title = "The Phantom Menace"; + String bar2Title = "The Clone Wars"; + vs.setValue("barCollection(22).title", bar2Title); + vs.setValue("barCollection(11).title", bar1Title); + for (Object aBarColl : barColl) { + Bar next = (Bar) aBarColl; + if (next.getId().intValue() == 22) { + assertEquals(bar2Title, next.getTitle()); + } else { + assertEquals(bar1Title, next.getTitle()); + } + } + //now test adding to a collection + String bar3Title = "Revenge of the Sith"; + String bar4Title = "A New Hope"; + vs.setValue("barCollection.makeNew[4].title", bar4Title, true); + vs.setValue("barCollection.makeNew[0].title", bar3Title, true); + + assertEquals(4, barColl.size()); + + for (Object aBarColl : barColl) { + Bar next = (Bar) aBarColl; + if (next.getId() == null) { + assertNotNull(next.getTitle()); + assertTrue(next.getTitle().equals(bar4Title) + || next.getTitle().equals(bar3Title)); + } + } + + } + public void testAddingToCollectionBasedOnPermission() { + final MockObjectTypeDeterminer determiner = new MockObjectTypeDeterminer(Long.class,Bar.class,"id",true); + loadConfigurationProviders(new StubConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, + LocatableProperties props) throws ConfigurationException { + builder.factory(ObjectTypeDeterminer.class, new Factory() { + public Object create(Context context) throws Exception { + return determiner; + } + + }, Scope.SINGLETON); + } + }); + + Collection barColl=new HashSet(); + + ValueStack vs = ActionContext.getContext().getValueStack(); + ReflectionContextState.setCreatingNullObjects(vs.getContext(), true); + ReflectionContextState.setReportingConversionErrors(vs.getContext(), true); + Foo foo = new Foo(); + + foo.setBarCollection(barColl); + + vs.push(foo); + + String bar1Title="title"; + vs.setValue("barCollection(11).title", bar1Title); + + assertEquals(1, barColl.size()); + Object bar=barColl.iterator().next(); + assertTrue(bar instanceof Bar); + assertEquals(((Bar)bar).getTitle(), bar1Title); + assertEquals(((Bar)bar).getId(), new Long(11)); + + //now test where there is no permission + determiner.setShouldCreateIfNew(false); + + String bar2Title="another title"; + vs.setValue("barCollection(22).title", bar1Title); + + assertEquals(1, barColl.size()); + bar=barColl.iterator().next(); + assertTrue(bar instanceof Bar); + assertEquals(((Bar)bar).getTitle(), bar1Title); + assertEquals(((Bar)bar).getId(), new Long(11)); + + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java new file mode 100644 index 000000000..6942a1f9f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2005 Opensymphony. All Rights Reserved. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.ListHolder; +import com.opensymphony.xwork2.util.ValueStack; + +import java.util.ArrayList; +import java.util.List; + +/** + * XWorkListPropertyAccessorTest + *

+ * Created : Nov 7, 2005 3:54:44 PM + * + * @author Jason Carreira + */ +public class XWorkListPropertyAccessorTest extends XWorkTestCase { + + public void testContains() { + ValueStack vs = ActionContext.getContext().getValueStack(); + ListHolder listHolder = new ListHolder(); + vs.push(listHolder); + + vs.setValue("longs", new String[] {"1", "2", "3"}); + + assertNotNull(listHolder.getLongs()); + assertEquals(3, listHolder.getLongs().size()); + assertEquals(new Long(1), (Long) listHolder.getLongs().get(0)); + assertEquals(new Long(2), (Long) listHolder.getLongs().get(1)); + assertEquals(new Long(3), (Long) listHolder.getLongs().get(2)); + + assertTrue(((Boolean) vs.findValue("longs.contains(1)")).booleanValue()); + } + + public void testCanAccessListSizeProperty() { + ValueStack vs = ActionContext.getContext().getValueStack(); + List myList = new ArrayList(); + myList.add("a"); + myList.add("b"); + + ListHolder listHolder = new ListHolder(); + listHolder.setStrings(myList); + + vs.push(listHolder); + + assertEquals(new Integer(myList.size()), vs.findValue("strings.size()")); + assertEquals(new Integer(myList.size()), vs.findValue("strings.size")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java new file mode 100644 index 000000000..a41193f00 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java @@ -0,0 +1,78 @@ +/* + * Created on Jun 12, 2004 + */ +package com.opensymphony.xwork2.spring; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import org.springframework.context.ApplicationContext; + +/** + * Test loading actions from the Spring Application Context. + * + * @author Simon Stewart + */ +public class ActionsFromSpringTest extends XWorkTestCase { + private ApplicationContext appContext; + + @Override public void setUp() throws Exception { + super.setUp(); + + // Set up XWork + loadConfigurationProviders(new XmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml")); + appContext = ((SpringObjectFactory)container.getInstance(ObjectFactory.class)).appContext; + } + + public void testLoadSimpleAction() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "simpleAction", null); + Object action = proxy.getAction(); + + Action expected = (Action) appContext.getBean("simple-action"); + + assertEquals(expected.getClass(), action.getClass()); + } + + public void testLoadActionWithDependencies() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "dependencyAction", null); + SimpleAction action = (SimpleAction) proxy.getAction(); + + assertEquals("injected", action.getBlah()); + } + + public void testProxiedActionIsNotStateful() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "proxiedAction", null); + SimpleAction action = (SimpleAction) proxy.getAction(); + + action.setBlah("Hello World"); + + proxy = actionProxyFactory.createActionProxy(null, "proxiedAction", null); + action = (SimpleAction) proxy.getAction(); + + // If the action is a singleton, this test will fail + SimpleAction sa = new SimpleAction(); + assertEquals(sa.getBlah(), action.getBlah()); + + // And if the advice is not being applied, this will be SUCCESS. + String result = action.execute(); + assertEquals(Action.INPUT, result); + } + + public void testAutoProxiedAction() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "autoProxiedAction", null); + + SimpleAction action = (SimpleAction) proxy.getAction(); + + String result = action.execute(); + assertEquals(Action.INPUT, result); + } + + public void testActionWithSpringResult() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "simpleActionSpringResult", null); + + proxy.execute(); + + SpringResult springResult = (SpringResult) proxy.getInvocation().getResult(); + assertTrue(springResult.isInitialize()); + assertNotNull(springResult.getStringParameter()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/Bar.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/Bar.java new file mode 100644 index 000000000..932855057 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/Bar.java @@ -0,0 +1,56 @@ +/* + * Created on Nov 12, 2003 + */ +package com.opensymphony.xwork2.spring; + +/** + * @author Mike + */ +public class Bar { + + private Foo foo; + private String thing; + private int value; + + /** + * @return Returns the foo. + */ + public Foo getFoo() { + return foo; + } + + /** + * @param foo The foo to set. + */ + public void setFoo(Foo foo) { + this.foo = foo; + } + + /** + * @return Returns the thing. + */ + public String getThing() { + return thing; + } + + /** + * @param thing The thing to set. + */ + public void setThing(String thing) { + this.thing = thing; + } + + /** + * @return Returns the value. + */ + public int getValue() { + return value; + } + + /** + * @param value The value to set. + */ + public void setValue(int value) { + this.value = value; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ExecuteInterceptor.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ExecuteInterceptor.java new file mode 100644 index 000000000..6f618a3d4 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ExecuteInterceptor.java @@ -0,0 +1,21 @@ +/* + * Created on Jun 12, 2004 + */ +package com.opensymphony.xwork2.spring; + +import com.opensymphony.xwork2.Action; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + + +/** + * @author Simon Stewart + */ +public class ExecuteInterceptor implements MethodInterceptor { + public Object invoke(MethodInvocation mi) throws Throwable { + if ("execute".equals(mi.getMethod().getName())) + return Action.INPUT; + return mi.proceed(); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ExternalReferenceAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ExternalReferenceAction.java new file mode 100644 index 000000000..c5068cd69 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ExternalReferenceAction.java @@ -0,0 +1,49 @@ +/* + * Created on Nov 11, 2003 + */ +package com.opensymphony.xwork2.spring; + +import com.opensymphony.xwork2.Action; + +/** + * @author Mike + */ +public class ExternalReferenceAction implements Action +{ + private Foo foo; + private Bar bar; + + public String execute() throws Exception { + return SUCCESS; + } + + /** + * @return Returns the foo. + */ + public Foo getFoo() { + return foo; + } + + /** + * @param foo + * The foo to set. + */ + public void setFoo(Foo foo) { + this.foo = foo; + } + + /** + * @return Returns the bar. + */ + public Bar getBar() { + return bar; + } + + /** + * @param bar + * The bar to set. + */ + public void setBar(Bar bar) { + this.bar = bar; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/Foo.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/Foo.java new file mode 100644 index 000000000..87502c785 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/Foo.java @@ -0,0 +1,30 @@ +/* + * Created on Nov 11, 2003 + */ +package com.opensymphony.xwork2.spring; + +/** + * @author Mike + */ +public class Foo +{ + String name = null; + + public Foo() { + name = "not set"; + } + + public Foo(String name) { + this.name = name; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/SpringObjectFactoryTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/SpringObjectFactoryTest.java new file mode 100644 index 000000000..638656ff2 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/SpringObjectFactoryTest.java @@ -0,0 +1,373 @@ +package com.opensymphony.xwork2.spring; + +/* + * 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. + */ +/* + * Created on Mar 8, 2004 + */ + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.interceptor.Interceptor; +import com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor; +import com.opensymphony.xwork2.interceptor.TimerInterceptor; +import com.opensymphony.xwork2.test.StubConfigurationProvider; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import com.opensymphony.xwork2.validator.Validator; +import com.opensymphony.xwork2.validator.validators.ExpressionValidator; +import com.opensymphony.xwork2.validator.validators.RequiredStringValidator; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; +import org.springframework.aop.interceptor.DebugInterceptor; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.support.StaticApplicationContext; + +import java.util.HashMap; + +// TODO: Document properly + +/** + * @author Simon Stewart + */ +public class SpringObjectFactoryTest extends XWorkTestCase { + + StaticApplicationContext sac; + SpringObjectFactory objectFactory; + + + @Override + public void setUp() throws Exception { + super.setUp(); + + sac = new StaticApplicationContext(); + loadConfigurationProviders(new StubConfigurationProvider() { + + @Override + public void register(ContainerBuilder builder, + LocatableProperties props) throws ConfigurationException { + builder.factory(ObjectFactory.class, SpringObjectFactory.class); + } + + }); + + objectFactory = (SpringObjectFactory) container.getInstance(ObjectFactory.class); + objectFactory.setApplicationContext(sac); + objectFactory.setAlwaysRespectAutowireStrategy(false); + } + + @Override + public void tearDown() throws Exception { + sac = null; + objectFactory = null; + } + + public void testFallsBackToDefaultObjectFactoryActionSearching() throws Exception { + ActionConfig actionConfig = new ActionConfig.Builder("foo", "bar", ModelDrivenAction.class.getName()).build(); + + Object action = objectFactory.buildBean(actionConfig.getClassName(), null); + + assertEquals(ModelDrivenAction.class, action.getClass()); + } + + public void testFallsBackToDefaultObjectFactoryInterceptorBuilding() throws Exception { + InterceptorConfig iConfig = new InterceptorConfig.Builder("timer", ModelDrivenInterceptor.class.getName()).build(); + + Interceptor interceptor = objectFactory.buildInterceptor(iConfig, new HashMap()); + + assertEquals(ModelDrivenInterceptor.class, interceptor.getClass()); + } + + public void testFallsBackToDefaultObjectFactoryResultBuilding() throws Exception { + ResultConfig rConfig = new ResultConfig.Builder(Action.SUCCESS, ActionChainResult.class.getName()).build(); + Result result = objectFactory.buildResult(rConfig, ActionContext.getContext().getContextMap()); + + assertEquals(ActionChainResult.class, result.getClass()); + } + + public void testFallsBackToDefaultObjectFactoryValidatorBuilding() throws Exception { + Validator validator = objectFactory.buildValidator(RequiredStringValidator.class.getName(), new HashMap(), null); + + assertEquals(RequiredStringValidator.class, validator.getClass()); + } + + public void testObtainActionBySpringName() throws Exception { + sac.registerPrototype("simple-action", SimpleAction.class, new MutablePropertyValues()); + + ActionConfig actionConfig = new ActionConfig.Builder("fs", "jim", "simple-action").build(); + Object action = objectFactory.buildBean(actionConfig.getClassName(), null); + + assertEquals(SimpleAction.class, action.getClass()); + } + + public void testObtainInterceptorBySpringName() throws Exception { + sac.registerSingleton("timer-interceptor", TimerInterceptor.class, new MutablePropertyValues()); + + InterceptorConfig iConfig = new InterceptorConfig.Builder("timer", "timer-interceptor").build(); + Interceptor interceptor = objectFactory.buildInterceptor(iConfig, new HashMap()); + + assertEquals(TimerInterceptor.class, interceptor.getClass()); + } + + public void testObtainResultBySpringName() throws Exception { + // TODO: Does this need to be a prototype? + sac.registerPrototype("chaining-result", ActionChainResult.class, new MutablePropertyValues()); + + ResultConfig rConfig = new ResultConfig.Builder(Action.SUCCESS, "chaining-result").build(); + Result result = objectFactory.buildResult(rConfig, ActionContext.getContext().getContextMap()); + + assertEquals(ActionChainResult.class, result.getClass()); + } + + public void testObtainValidatorBySpringName() throws Exception { + sac.registerPrototype("expression-validator", ExpressionValidator.class, new MutablePropertyValues()); + + Validator validator = objectFactory.buildValidator("expression-validator", new HashMap(), null); + + assertEquals(ExpressionValidator.class, validator.getClass()); + } + + public void testShouldAutowireObjectsObtainedFromTheObjectFactoryByFullClassName() throws Exception { + sac.getBeanFactory().registerSingleton("bean", new TestBean()); + TestBean bean = (TestBean) sac.getBean("bean"); + + SimpleAction action = (SimpleAction) objectFactory.buildBean(SimpleAction.class.getName(), null); + + assertEquals(bean, action.getBean()); + } + + public void testShouldGiveReferenceToAppContextIfBeanIsApplicationContextAwareAndNotInstantiatedViaSpring() throws Exception { + Foo foo = (Foo) objectFactory.buildBean(Foo.class.getName(), null); + + assertTrue("Expected app context to have been set", foo.isApplicationContextSet()); + } + + public static class Foo implements ApplicationContextAware { + boolean applicationContextSet = false; + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + applicationContextSet = true; + } + + public boolean isApplicationContextSet() { + return applicationContextSet; + } + } + + public void testShouldAutowireObjectsObtainedFromTheObjectFactoryByClass() throws Exception { + sac.getBeanFactory().registerSingleton("bean", new TestBean()); + TestBean bean = (TestBean) sac.getBean("bean"); + + SimpleAction action = (SimpleAction) objectFactory.buildBean(SimpleAction.class, null); + + assertEquals(bean, action.getBean()); + } + + public void testShouldGiveReferenceToAppContextIfBeanIsLoadedByClassApplicationContextAwareAndNotInstantiatedViaSpring() throws Exception { + Foo foo = (Foo) objectFactory.buildBean(Foo.class, null); + + assertTrue("Expected app context to have been set", foo.isApplicationContextSet()); + } + + public void testLookingUpAClassInstanceDelegatesToSpring() throws Exception { + sac.registerPrototype("simple-action", SimpleAction.class, new MutablePropertyValues()); + + Class clazz = objectFactory.getClassInstance("simple-action"); + + assertNotNull("Nothing returned", clazz); + assertEquals("Expected to have instance of SimpleAction returned", SimpleAction.class, clazz); + } + + public void testLookingUpAClassInstanceFallsBackToTheDefaultObjectFactoryIfSpringBeanNotFound() throws Exception { + Class clazz = objectFactory.getClassInstance(SimpleAction.class.getName()); + + assertNotNull("Nothing returned", clazz); + assertEquals("Expected to have instance of SimpleAction returned", SimpleAction.class, clazz); + } + + public void testSetAutowireStrategy() throws Exception { + assertEquals(objectFactory.getAutowireStrategy(), AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); + + objectFactory.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); + + sac.getBeanFactory().registerSingleton("bean", new TestBean()); + TestBean bean = (TestBean) sac.getBean("bean"); + + sac.registerPrototype("simple-action", SimpleAction.class, new MutablePropertyValues()); + + ActionConfig actionConfig = new ActionConfig.Builder("jim", "bob", "simple-action").build(); + SimpleAction simpleAction = (SimpleAction) objectFactory.buildBean(actionConfig.getClassName(), null); + objectFactory.autoWireBean(simpleAction); + assertEquals(simpleAction.getBean(), bean); + } + + public void testShouldUseConstructorBasedInjectionWhenCreatingABeanFromAClassName() throws Exception { + SpringObjectFactory factory = (SpringObjectFactory) objectFactory; + objectFactory.setAlwaysRespectAutowireStrategy(false); + sac.registerSingleton("actionBean", SimpleAction.class, new MutablePropertyValues()); + + ConstructorBean bean = (ConstructorBean) factory.buildBean(ConstructorBean.class, null); + + assertNotNull("Bean should not be null", bean); + assertNotNull("Action should have been added via DI", bean.getAction()); + } + + public void testShouldUseAutowireStrategyWhenCreatingABeanFromAClassName_constructor() throws Exception { + objectFactory.setAlwaysRespectAutowireStrategy(true); + objectFactory.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); + sac.registerSingleton("actionBean", SimpleAction.class, new MutablePropertyValues()); + + ConstructorBean bean = (ConstructorBean) objectFactory.buildBean(ConstructorBean.class, null); + + assertNotNull("Bean should not be null", bean); + assertNotNull("Action should have been added via DI", bean.getAction()); + } + + public void testShouldUseAutowireStrategyWhenCreatingABeanFromAClassName_setterByType() throws Exception { + objectFactory.setAlwaysRespectAutowireStrategy(true); + + objectFactory.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); + sac.registerSingleton("actionBean", SimpleAction.class, new MutablePropertyValues()); + + SetterByTypeBean bean = (SetterByTypeBean) objectFactory.buildBean(SetterByTypeBean.class, null); + + assertNotNull("Bean should not be null", bean); + assertNotNull("Action should have been added via DI", bean.getAction()); + } + + public void testShouldUseAutowireStrategyWhenCreatingABeanFromAClassName_setterByName() throws Exception { + objectFactory.setAlwaysRespectAutowireStrategy(true); + + objectFactory.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); + sac.registerSingleton("actionBean", SimpleAction.class, new MutablePropertyValues()); + + SetterByNameBean bean = (SetterByNameBean) objectFactory.buildBean(SetterByNameBean.class, null); + + assertNotNull("Bean should not be null", bean); + assertNotNull("Action should have been added via DI", bean.getActionBean()); + } + + public void testFallBackToDefaultObjectFactoryWhenTheCConstructorDIIsAmbiguous() throws Exception { + objectFactory.setAlwaysRespectAutowireStrategy(true); + sac.registerSingleton("firstActionBean", SimpleAction.class, new MutablePropertyValues()); + sac.registerSingleton("secondActionBean", SimpleAction.class, new MutablePropertyValues()); + + ConstructorBean bean = (ConstructorBean) objectFactory.buildBean(ConstructorBean.class, null); + + assertNotNull("Bean should have been created using default constructor", bean); + assertNull("Not expecting this to have been set", bean.getAction()); + } + + public void testObjectFactoryUsesSpringObjectFactoryToCreateActions() throws Exception { + sac.registerSingleton("actionBean", SimpleAction.class, new MutablePropertyValues()); + ActionConfig actionConfig = new ActionConfig.Builder("as", "as", ConstructorAction.class.getName()).build(); + + ConstructorAction action = (ConstructorAction) objectFactory.buildBean(actionConfig.getClassName(), null); + + assertNotNull("Bean should not be null", action); + assertNotNull("Action should have been added via DI", action.getAction()); + } + + public void testShouldUseApplicationContextToApplyAspectsToGeneratedBeans() throws Exception { + sac.registerSingleton("debugInterceptor", DebugInterceptor.class, new MutablePropertyValues()); + + MutablePropertyValues values = new MutablePropertyValues(); + values.addPropertyValue("beanNames", new String[]{"*Action"}); + values.addPropertyValue("interceptorNames", new String[]{"debugInterceptor"}); + sac.registerSingleton("proxyFactory", BeanNameAutoProxyCreator.class, values); + + sac.refresh(); + + ActionConfig actionConfig = new ActionConfig.Builder("", "", SimpleAction.class.getName()).build(); + Action action = (Action) objectFactory.buildBean(actionConfig.getClassName(), null); + + assertNotNull("Bean should not be null", action); + System.out.println("Action class is: " + action.getClass().getName()); + assertTrue("Action should have been advised", action instanceof Advised); + } + + public static class ConstructorBean { + private SimpleAction action; + + public ConstructorBean() { + // Empty constructor + } + + public ConstructorBean(SimpleAction action) { + this.action = action; + } + + public SimpleAction getAction() { + return action; + } + } + + public static class SetterByNameBean { + private SimpleAction action; + + public SetterByNameBean() { + // Empty constructor + } + + public SimpleAction getActionBean() { + return action; + } + + public void setActionBean(SimpleAction action) { + this.action = action; + } + } + + public static class SetterByTypeBean { + private SimpleAction action; + + public SetterByTypeBean() { + // Empty constructor + } + + public SimpleAction getAction() { + return action; + } + + public void setAction(SimpleAction action) { + this.action = action; + } + } + + public static class ConstructorAction implements Action { + private SimpleAction action; + + public ConstructorAction(SimpleAction action) { + this.action = action; + } + + public String execute() throws Exception { + return SUCCESS; + } + + public SimpleAction getAction() { + return action; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/SpringResult.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/SpringResult.java new file mode 100644 index 000000000..930152e77 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/SpringResult.java @@ -0,0 +1,36 @@ +package com.opensymphony.xwork2.spring; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.Result; + +public class SpringResult implements Result { + + private static final long serialVersionUID = -2877126768401198951L; + + private boolean initialize = false; + + // this String should be populated by spring + private String stringParameter; + + public void initialize() { + // this method should be called by spring + this.initialize = true; + } + + public void execute(ActionInvocation invocation) throws Exception { + // intetionally empty + } + + public void setStringParameter(String stringParameter) { + this.stringParameter = stringParameter; + } + + public String getStringParameter() { + return this.stringParameter; + } + + public boolean isInitialize() { + return this.initialize; + } +} + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptorTest.java new file mode 100644 index 000000000..35966ebfc --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptorTest.java @@ -0,0 +1,110 @@ +/* + * Created on 6/11/2004 + */ +package com.opensymphony.xwork2.spring.interceptor; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Simon Stewart + */ +public class ActionAutowiringInterceptorTest extends XWorkTestCase { + + public void testShouldAutowireAction() throws Exception { + StaticWebApplicationContext context = new StaticWebApplicationContext(); + context.getBeanFactory().registerSingleton("bean", new TestBean()); + TestBean bean = (TestBean) context.getBean("bean"); + + loadSpringApplicationContextIntoApplication(context); + + SimpleAction action = new SimpleAction(); + ActionInvocation invocation = new TestActionInvocation(action); + + ActionAutowiringInterceptor interceptor = new ActionAutowiringInterceptor(); + interceptor.setApplicationContext(context); + interceptor.init(); + + interceptor.intercept(invocation); + + assertEquals(bean, action.getBean()); + } + + public void testSetAutowireType() throws Exception { + XmlConfigurationProvider prov = new XmlConfigurationProvider("xwork-default.xml"); + prov.setThrowExceptionOnDuplicateBeans(false); + XmlConfigurationProvider c = new XmlConfigurationProvider("com/opensymphony/xwork2/spring/xwork-autowire.xml"); + loadConfigurationProviders(c, prov); + + StaticWebApplicationContext appContext = new StaticWebApplicationContext(); + + loadSpringApplicationContextIntoApplication(appContext); + + ActionAutowiringInterceptor interceptor = new ActionAutowiringInterceptor(); + interceptor.init(); + + SimpleAction action = new SimpleAction(); + ActionInvocation invocation = new TestActionInvocation(action); + + interceptor.intercept(invocation); + + ApplicationContext loadedContext = interceptor.getApplicationContext(); + + assertEquals(appContext, loadedContext); + } + + protected void loadSpringApplicationContextIntoApplication(ApplicationContext appContext) { + Map application = new HashMap(); + application.put(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, appContext); + + Map context = new HashMap(); + context.put(ActionContext.APPLICATION, application); + ActionContext actionContext = new ActionContext(context); + ActionContext.setContext(actionContext); + } + + public void testLoadsApplicationContextUsingWebApplicationContextUtils() throws Exception { + StaticWebApplicationContext appContext = new StaticWebApplicationContext(); + + loadSpringApplicationContextIntoApplication(appContext); + + ActionAutowiringInterceptor interceptor = new ActionAutowiringInterceptor(); + interceptor.init(); + + SimpleAction action = new SimpleAction(); + ActionInvocation invocation = new TestActionInvocation(action); + + interceptor.intercept(invocation); + + ApplicationContext loadedContext = interceptor.getApplicationContext(); + + assertEquals(appContext, loadedContext); + } + + public void testIfApplicationContextIsNullThenBeanWillNotBeWiredUp() throws Exception { + Map context = new HashMap(); + context.put(ActionContext.APPLICATION, new HashMap()); + ActionContext actionContext = new ActionContext(context); + ActionContext.setContext(actionContext); + + ActionAutowiringInterceptor interceptor = new ActionAutowiringInterceptor(); + interceptor.init(); + + SimpleAction action = new SimpleAction(); + ActionInvocation invocation = new TestActionInvocation(action); + TestBean bean = action.getBean(); + + // If an exception is thrown here, things are going to go wrong in + // production + interceptor.intercept(invocation); + + assertEquals(bean, action.getBean()); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/interceptor/TestActionInvocation.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/interceptor/TestActionInvocation.java new file mode 100644 index 000000000..4951632ec --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/interceptor/TestActionInvocation.java @@ -0,0 +1,73 @@ +/* + * Created on 6/11/2004 + */ +package com.opensymphony.xwork2.spring.interceptor; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.util.ValueStack; + +import java.lang.reflect.Method; + +/** + * @author Simon Stewart + */ +public class TestActionInvocation implements ActionInvocation { + private Object action; + private boolean executed; + + public TestActionInvocation(Object wrappedAction) { + this.action = wrappedAction; + } + + public Object getAction() { + return action; + } + + public boolean isExecuted() { + return executed; + } + + public ActionContext getInvocationContext() { + return null; + } + + public ActionProxy getProxy() { + return null; + } + + public Result getResult() throws Exception { + return null; + } + + public String getResultCode() { + return null; + } + + public void setResultCode(String resultCode) { + + } + + public ValueStack getStack() { + return null; + } + + public void addPreResultListener(PreResultListener listener) { + } + + public String invoke() throws Exception { + return invokeActionOnly(); + } + + public String invokeActionOnly() throws Exception { + executed = true; + Method method = action.getClass().getMethod("execute", new Class[0]); + return (String) method.invoke(action, new Object[0]); + } + + public void setActionEventListener(ActionEventListener listener) { + } + + public void init(ActionProxy proxy) { + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationDataAware.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationDataAware.java new file mode 100644 index 000000000..fd63584f7 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationDataAware.java @@ -0,0 +1,48 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.conversion.annotations.Conversion; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator; +import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator; +import com.opensymphony.xwork2.validator.annotations.Validation; + + +/** + * Implemented by SimpleAction3 and AnnotationTestBean2 to test class hierarchy traversal. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +@Validation() +@Conversion() +public interface AnnotationDataAware { + + void setBarObj(Bar b); + + @TypeConversion( + converter = "com.opensymphony.xwork2.conversion.impl.FooBarConverter" + ) + Bar getBarObj(); + + @RequiredFieldValidator(message = "You must enter a value for data.") + @RequiredStringValidator(message = "You must enter a value for data.") + void setData(String data); + + String getData(); +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationDataAware2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationDataAware2.java new file mode 100644 index 000000000..b075fcd27 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationDataAware2.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.test; + +import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator; + + +/** + * Used to test hierarchy traversal for interfaces. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +public interface AnnotationDataAware2 extends AnnotationDataAware { + + @RequiredStringValidator(message = "You must enter a value for data.") + public void setBling(String bling); + + public String getBling(); +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationTestBean2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationTestBean2.java new file mode 100644 index 000000000..3a1396b9e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationTestBean2.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.test; + +import com.opensymphony.xwork2.AnnotatedTestBean; +import com.opensymphony.xwork2.conversion.annotations.Conversion; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.util.Cat; + + +/** + * Extend TestBean to test class hierarchy traversal. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +@Conversion() +public class AnnotationTestBean2 extends AnnotatedTestBean implements AnnotationDataAware { + + private Bar bar; + private String data; + private Cat cat; + + + public void setBarObj(Bar b) { + bar = b; + } + + public Bar getBarObj() { + return bar; + } + + public void setData(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public Cat getCat() { + return cat; + } + + @TypeConversion( + key = "cat", converter = "com.opensymphony.xwork2.conversion.impl.FooBarConverter" + ) + public void setCat(Cat cat) { + this.cat = cat; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationUser.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationUser.java new file mode 100644 index 000000000..be2f86361 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationUser.java @@ -0,0 +1,107 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.conversion.annotations.ConversionRule; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.util.KeyProperty; +import com.opensymphony.xwork2.validator.annotations.*; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +/** + * Test bean. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +@Validation( + validations = @Validations( + expressions = { + @ExpressionValidator(expression = "email.startsWith('mark')", message = "Email does not start with mark"), + @ExpressionValidator(expression = "email2.startsWith('mark')", message = "Email2 does not start with mark") + } + ) +) +public class AnnotationUser implements AnnotationUserMarker { + + private Collection collection; + private List list; + private Map map; + private String email; + private String email2; + private String name; + + + public void setCollection(Collection collection) { + this.collection = collection; + } + + public Collection getCollection() { + return collection; + } + + @EmailValidator(shortCircuit = true, message = "Not a valid e-mail.") + @FieldExpressionValidator(expression = "email.endsWith('mycompany.com')", message = "Email not from the right company.") + public void setEmail(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } + + @EmailValidator(message = "Not a valid e-mail2.") + @FieldExpressionValidator(expression = "email2.endsWith('mycompany.com')", message = "Email2 not from the right company.") + public void setEmail2(String email) { + email2 = email; + } + + public String getEmail2() { + return email2; + } + + public void setList(List l) { + list = l; + } + + @KeyProperty( value = "name") + @TypeConversion( converter = "java.lang.String", rule = ConversionRule.COLLECTION) + public List getList() { + return list; + } + + @TypeConversion( converter = "java.lang.String", rule = ConversionRule.MAP) + public void setMap(Map m) { + map = m; + } + + public Map getMap() { + return map; + } + + @RequiredFieldValidator(key = "name.key", message = "You must enter a value for name.") + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationUserMarker.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationUserMarker.java new file mode 100644 index 000000000..e0d6b4d33 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/AnnotationUserMarker.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.test; + +import com.opensymphony.xwork2.validator.annotations.ExpressionValidator; +import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator; +import com.opensymphony.xwork2.validator.annotations.Validation; +import com.opensymphony.xwork2.validator.annotations.Validations; + +/** + * Marker interface to help test hierarchy traversal. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +@Validation( + validations = @Validations( + requiredFields = { + @RequiredFieldValidator(fieldName = "email", shortCircuit = true, message = "You must enter a value for email."), + @RequiredFieldValidator(fieldName = "email2", shortCircuit = true, message = "You must enter a value for email2.") + }, + expressions = { + @ExpressionValidator(shortCircuit = true, expression = "email.equals(email2)", message = "Email not the same as email2" ) + } + ) +) +public interface AnnotationUserMarker { +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/DataAware.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/DataAware.java new file mode 100644 index 000000000..5a5de5ed3 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/DataAware.java @@ -0,0 +1,35 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.util.Bar; + + +/** + * Implemented by SimpleAction3 and TestBean2 to test class hierarchy traversal. + * + * @author Mark Woon + */ +public interface DataAware { + + void setBarObj(Bar b); + + Bar getBarObj(); + + void setData(String data); + + String getData(); +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/DataAware2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/DataAware2.java new file mode 100644 index 000000000..4a65fe084 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/DataAware2.java @@ -0,0 +1,29 @@ +/* + * 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.test; + + +/** + * Used to test hierarchy traversal for interfaces. + * + * @author Mark Woon + */ +public interface DataAware2 extends DataAware { + + public void setBling(String bling); + + public String getBling(); +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/Equidae.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/Equidae.java new file mode 100644 index 000000000..e0048758e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/Equidae.java @@ -0,0 +1,52 @@ +/* + * 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.test; + + +/** + * @author Mark Woon + */ +public class Equidae { + + private String cow; + private String donkey; + private String horse; + + + public void setCow(String cow) { + this.cow = cow; + } + + public String getCow() { + return cow; + } + + public void setDonkey(String donkey) { + this.donkey = donkey; + } + + public String getDonkey() { + return donkey; + } + + public void setHorse(String horse) { + this.horse = horse; + } + + public String getHorse() { + return horse; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAction2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAction2.java new file mode 100644 index 000000000..5c21e083c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAction2.java @@ -0,0 +1,38 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.ModelDrivenAction; + + +/** + * Extend ModelDrivenAction to test class hierarchy traversal. + * + * @author Mark Woon + */ +public class ModelDrivenAction2 extends ModelDrivenAction { + + private TestBean2 model = new TestBean2(); + + + /** + * @return the model to be pushed onto the ValueStack after the Action itself + */ + @Override + public Object getModel() { + return model; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAnnotationAction2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAnnotationAction2.java new file mode 100644 index 000000000..038e2eea9 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAnnotationAction2.java @@ -0,0 +1,39 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.ModelDrivenAnnotationAction; + + +/** + * Extend ModelDrivenAction to test class hierarchy traversal. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +public class ModelDrivenAnnotationAction2 extends ModelDrivenAnnotationAction { + + private AnnotationTestBean2 model = new AnnotationTestBean2(); + + + /** + * @return the model to be pushed onto the ValueStack after the Action itself + */ + @Override + public Object getModel() { + return model; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAction2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAction2.java new file mode 100644 index 000000000..dea7539c2 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAction2.java @@ -0,0 +1,39 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.SimpleAction; + + +/** + * SimpleAction2 + * + * @author Jason Carreira + * Created Jun 14, 2003 9:51:12 PM + */ +public class SimpleAction2 extends SimpleAction { + + private int count; + + + public void setCount(int count) { + this.count = count; + } + + public int getCount() { + return count; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAction3.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAction3.java new file mode 100644 index 000000000..ba060adb6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAction3.java @@ -0,0 +1,48 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.util.Bar; + + +/** + * Extend SimpleAction to test class hierarchy traversal. + * + * @author Mark Woon + */ +public class SimpleAction3 extends SimpleAction implements DataAware { + + private Bar bar; + private String data; + + + public void setBarObj(Bar b) { + bar = b; + } + + public Bar getBarObj() { + return bar; + } + + public void setData(String data) { + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAnnotationAction2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAnnotationAction2.java new file mode 100644 index 000000000..8eb3c2d9f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAnnotationAction2.java @@ -0,0 +1,42 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.SimpleAnnotationAction; +import com.opensymphony.xwork2.validator.annotations.IntRangeFieldValidator; +import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator; + +/** + * SimpleAction2 + * + * @author Jason Carreira + * @author Rainer Hermanns + * Created Jun 14, 2003 9:51:12 PM + */ +public class SimpleAnnotationAction2 extends SimpleAnnotationAction { + + private int count; + + @RequiredFieldValidator(message = "You must enter a value for count.") + @IntRangeFieldValidator(min = "0", max = "5", message = "count must be between ${min} and ${max}, current value is ${count}.") + public void setCount(int count) { + this.count = count; + } + + public int getCount() { + return count; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAnnotationAction3.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAnnotationAction3.java new file mode 100644 index 000000000..85e8438be --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/SimpleAnnotationAction3.java @@ -0,0 +1,49 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.SimpleAnnotationAction; +import com.opensymphony.xwork2.util.Bar; + + +/** + * Extend SimpleAction to test class hierarchy traversal. + * + * @author Mark Woon + * @author Rainer Hermanns + */ +public class SimpleAnnotationAction3 extends SimpleAnnotationAction implements AnnotationDataAware { + + private Bar bar; + private String data; + + + public void setBarObj(Bar b) { + bar = b; + } + + public Bar getBarObj() { + return bar; + } + + public void setData(String data) { + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/TestBean2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/TestBean2.java new file mode 100644 index 000000000..89e2a04ab --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/TestBean2.java @@ -0,0 +1,58 @@ +/* + * 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.test; + +import com.opensymphony.xwork2.TestBean; +import com.opensymphony.xwork2.util.Bar; +import com.opensymphony.xwork2.util.Cat; + + +/** + * Extend TestBean to test class hierarchy traversal. + * + * @author Mark Woon + */ +public class TestBean2 extends TestBean implements DataAware { + + private Bar bar; + private String data; + private Cat cat; + + + public void setBarObj(Bar b) { + bar = b; + } + + public Bar getBarObj() { + return bar; + } + + public void setData(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public Cat getCat() { + return cat; + } + + public void setCat(Cat cat) { + this.cat = cat; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/User.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/User.java new file mode 100644 index 000000000..ca7219798 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/User.java @@ -0,0 +1,85 @@ +/* + * 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.test; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +/** + * Test bean. + * + * @author Mark Woon + */ +public class User implements UserMarker { + + private Collection collection; + private List list; + private Map map; + private String email; + private String email2; + private String name; + + + public void setCollection(Collection collection) { + this.collection = collection; + } + + public Collection getCollection() { + return collection; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } + + public void setEmail2(String email) { + email2 = email; + } + + public String getEmail2() { + return email2; + } + + public void setList(List l) { + list = l; + } + + public List getList() { + return list; + } + + public void setMap(Map m) { + map = m; + } + + public Map getMap() { + return map; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/UserMarker.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/UserMarker.java new file mode 100644 index 000000000..26818b2de --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/UserMarker.java @@ -0,0 +1,25 @@ +/* + * 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.test; + + +/** + * Marker interface to help test hierarchy traversal. + * + * @author Mark Woon + */ +public interface UserMarker { +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/Address.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/Address.java new file mode 100644 index 000000000..8fe3bc8c6 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/Address.java @@ -0,0 +1,40 @@ +package com.opensymphony.xwork2.test.annotations; + +public class Address { + private String line1; + private String line2; + private String city; + private String country; + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/AddressTypeConverter.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/AddressTypeConverter.java new file mode 100644 index 000000000..f0eadebf0 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/AddressTypeConverter.java @@ -0,0 +1,29 @@ +package com.opensymphony.xwork2.test.annotations; + +import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter; + +import java.util.Map; + +public class AddressTypeConverter extends DefaultTypeConverter { + @Override public Object convertValue(Map context, Object value, Class toType) { + if(value instanceof String) { + return decodeAddress((String)value); + } else if(value instanceof String && value.getClass().isArray()) { + return decodeAddress(((String[])value)[0]); + } else { + Address address = (Address)value; + return address.getLine1() + ":" + address.getLine2() + ":" + + address.getCity() + ":" + address.getCountry(); + } + } + + private Address decodeAddress(String encodedAddress) { + String[] parts = ((String)encodedAddress).split(":"); + Address address = new Address(); + address.setLine1(parts[0]); + address.setLine2(parts[1]); + address.setCity(parts[2]); + address.setCountry(parts[3]); + return address; + } +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/Person.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/Person.java new file mode 100644 index 000000000..4c47ee8f1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/Person.java @@ -0,0 +1,22 @@ +package com.opensymphony.xwork2.test.annotations; + +public class Person { + private String firstName; + private String lastName; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonAction.java new file mode 100644 index 000000000..72e20ef8e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonAction.java @@ -0,0 +1,47 @@ +package com.opensymphony.xwork2.test.annotations; + +import com.opensymphony.xwork2.conversion.annotations.Conversion; +import com.opensymphony.xwork2.conversion.annotations.ConversionType; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.util.Element; + +import java.util.List; + +@Conversion( + conversions={ + @TypeConversion(type=ConversionType.APPLICATION, + key="com.opensymphony.xwork2.test.annotations.Address", + converter="com.opensymphony.xwork2.test.annotations.AddressTypeConverter"), + @TypeConversion(type=ConversionType.APPLICATION, + key="com.opensymphony.xwork2.test.annotations.Person", + converter="com.opensymphony.xwork2.test.annotations.PersonTypeConverter")}) +public class PersonAction { + List users; + private List

address; + @Element(com.opensymphony.xwork2.test.annotations.Address.class) + private List addressesNoGenericElementAnnotation; + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public void setAddress(List
address) { + this.address = address; + } + + public List
getAddress() { + return address; + } + + public void setAddressesNoGenericElementAnnotation(List addressesNoGenericElementAnnotation) { + this.addressesNoGenericElementAnnotation = addressesNoGenericElementAnnotation; + } + + public List getAddressesNoGenericElementAnnotation() { + return addressesNoGenericElementAnnotation; + } +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonActionTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonActionTest.java new file mode 100644 index 000000000..2f18b4d15 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonActionTest.java @@ -0,0 +1,87 @@ +package com.opensymphony.xwork2.test.annotations; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; + +import java.util.Map; + + +public class PersonActionTest extends XWorkTestCase { + + public void testAddPerson() { + ValueStack stack = ActionContext.getContext().getValueStack(); + + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + PersonAction action = new PersonAction(); + stack.push(action); + + stack.setValue("users", "jonathan:gerrish"); + assertNotNull(action.getUsers()); + assertEquals(1, action.getUsers().size()); + + for(Object person : action.getUsers()) { + System.out.println("Person: " + person); + } + + assertEquals(Person.class, action.getUsers().get(0).getClass()); + assertEquals("jonathan", action.getUsers().get(0).getFirstName()); + assertEquals("gerrish", action.getUsers().get(0).getLastName()); + } + + public void testAddAddress() { + ValueStack stack = ActionContext.getContext().getValueStack(); + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + PersonAction action = new PersonAction(); + stack.push(action); + + stack.setValue("address", "2 Chandos Court:61 Haverstock Hill:London:England"); + assertNotNull(action.getAddress()); + assertEquals(1, action.getAddress().size()); + + for(Object address : action.getAddress()) { + System.out.println("Address: " + address); + } + + assertEquals(Address.class, action.getAddress().get(0).getClass()); + assertEquals("2 Chandos Court", action.getAddress().get(0).getLine1()); + assertEquals("61 Haverstock Hill", action.getAddress().get(0).getLine2()); + assertEquals("London", action.getAddress().get(0).getCity()); + assertEquals("England", action.getAddress().get(0).getCountry()); + } + + public void testAddAddressesNoGenericElementAnnotation() { + ValueStack stack = ActionContext.getContext().getValueStack(); + Map stackContext = stack.getContext(); + stackContext.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE); + stackContext.put(ReflectionContextState.DENY_METHOD_EXECUTION, Boolean.TRUE); + stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); + + PersonAction action = new PersonAction(); + stack.push(action); + + stack.setValue("addressesNoGenericElementAnnotation", "2 Chandos Court:61 Haverstock Hill:London:England"); + assertNotNull(action.getAddressesNoGenericElementAnnotation()); + assertEquals(1, action.getAddressesNoGenericElementAnnotation().size()); + + for(Object address : action.getAddressesNoGenericElementAnnotation()) { + System.out.println("Address: " + address); + } + + assertEquals(Address.class, action.getAddressesNoGenericElementAnnotation().get(0).getClass()); + assertEquals("2 Chandos Court", ((Address)action.getAddressesNoGenericElementAnnotation().get(0)).getLine1()); + assertEquals("61 Haverstock Hill", ((Address)action.getAddressesNoGenericElementAnnotation().get(0)).getLine2()); + assertEquals("London", ((Address)action.getAddressesNoGenericElementAnnotation().get(0)).getCity()); + assertEquals("England", ((Address)action.getAddressesNoGenericElementAnnotation().get(0)).getCountry()); + } +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonTypeConverter.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonTypeConverter.java new file mode 100644 index 000000000..6e27615c7 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/PersonTypeConverter.java @@ -0,0 +1,27 @@ +package com.opensymphony.xwork2.test.annotations; + +import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter; + +import java.util.Map; + +public class PersonTypeConverter extends DefaultTypeConverter { + @Override + public Object convertValue(Map context, Object value, Class toType) { + if(value instanceof String) { + return decodePerson((String)value); + } else if(value instanceof String && value.getClass().isArray()) { + return decodePerson(((String[])value)[0]); + } else { + Person person = (Person)value; + return person.getFirstName() + ":" + person.getLastName(); + } + } + + private Person decodePerson(String encodedPerson) { + String[] parts = ((String)encodedPerson).split(":"); + Person person = new Person(); + person.setFirstName(parts[0]); + person.setLastName(parts[1]); + return person; + } +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/ValidateAnnotatedMethodOnlyAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/ValidateAnnotatedMethodOnlyAction.java new file mode 100644 index 000000000..5e2bb2a5a --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/annotations/ValidateAnnotatedMethodOnlyAction.java @@ -0,0 +1,55 @@ +package com.opensymphony.xwork2.test.annotations; + +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.validator.annotations.ExpressionValidator; +import com.opensymphony.xwork2.validator.annotations.Validation; + +/** + * ValidateAnnotatedMethodOnlyAction + * + * @author Rainer Hermanns + * @version $Id$ + */ +@Validation +public class ValidateAnnotatedMethodOnlyAction extends ActionSupport { + + String param1; + String param2; + + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + public String getParam2() { + return param2; + } + + public void setParam2(String param2) { + this.param2 = param2; + } + + @ExpressionValidator(expression = "(param1 != null) || (param2 != null)", + message = "Need param1 or param2.") + public String annotatedMethod() { + try { + // do search + } catch (Exception e) { + return INPUT; + } + return SUCCESS; + } + + public String notAnnotatedMethod() { + try { + // do different search + } catch (Exception e) { + return INPUT; + } + return SUCCESS; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/test/subtest/NullModelDrivenAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/test/subtest/NullModelDrivenAction.java new file mode 100644 index 000000000..6836cb890 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/test/subtest/NullModelDrivenAction.java @@ -0,0 +1,19 @@ +package com.opensymphony.xwork2.test.subtest; + +import com.opensymphony.xwork2.ModelDrivenAction; + +/** + * Extends ModelDrivenAction to return a null model. + * + * @author Mark Woon + */ +public class NullModelDrivenAction extends ModelDrivenAction { + + /** + * @return the model to be pushed onto the ValueStack instead of the Action itself + */ + @Override + public Object getModel() { + return null; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/AnnotatedCat.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/AnnotatedCat.java new file mode 100644 index 000000000..573315967 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/AnnotatedCat.java @@ -0,0 +1,67 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.conversion.annotations.Conversion; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; + +import java.util.List; + + +/** + * @author Pat Lightbody + * @author $Author$ + * @author Rainer Hermanns + * @version $Revision$ + */ +@Conversion() +public class AnnotatedCat { + + public static final String SCIENTIFIC_NAME = "Feline"; + + + Foo foo; + List kittens; + String name; + + + public void setFoo(Foo foo) { + this.foo = foo; + } + + public Foo getFoo() { + return foo; + } + + public void setKittens(List kittens) { + this.kittens = kittens; + } + + @TypeConversion( + key = "kittens", converter = "com.opensymphony.xwork2.util.Cat" + ) + public List getKittens() { + return kittens; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java new file mode 100644 index 000000000..79ebb1e05 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java @@ -0,0 +1,101 @@ +package com.opensymphony.xwork2.util; + +import junit.framework.TestCase; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.AnnotatedElement; +import java.util.Collection; + +/** + * @author Dan Oxlade, dan d0t oxlade at gmail d0t c0m + */ +public class AnnotationUtilsTest extends TestCase { + + + public void testIsAnnotatedByWithoutAnnotationArgsReturnsFalse() throws Exception { + + assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class)); + assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"))); + + } + + @SuppressWarnings("unchecked") + public void testIsAnnotatedByWithSingleAnnotationArgMatchingReturnsTrue() throws Exception { + + assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); + + } + + @SuppressWarnings("unchecked") + public void testIsAnnotatedByWithMultiAnnotationArgMatchingReturnsTrue() throws Exception { + + assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class)); + assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class, Deprecated.class)); + assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class, MyAnnotation.class)); + + } + + public void testGetAnnotedMethodsWithoutAnnotationArgs() throws Exception { + + Collection ans = AnnotationUtils.getAnnotatedMethods(DummyClass.class); + + assertTrue(ans.size() == 1); + + assertEquals(ans.iterator().next(), DummyClass.class.getMethod("methodWithAnnotation")); + + } + + @SuppressWarnings("unchecked") + public void testGetAnnotatedMethodsWithAnnotationArgs() throws Exception { + + Collection ans = AnnotationUtils.getAnnotatedMethods(DummyClass.class, Deprecated.class); + assertTrue(ans.isEmpty()); + + ans = AnnotationUtils.getAnnotatedMethods(DummyClass.class, Deprecated.class, MyAnnotation.class); + assertEquals(1, ans.size()); + + ans = AnnotationUtils.getAnnotatedMethods(DummyClass.class, MyAnnotation.class); + assertEquals(1, ans.size()); + + ans = AnnotationUtils.getAnnotatedMethods(DummyClass.class, MyAnnotation.class, MyAnnotation2.class); + assertEquals(1, ans.size()); + + ans = AnnotationUtils.getAnnotatedMethods(DummyClassExt.class, MyAnnotation.class, MyAnnotation2.class); + assertEquals(2, ans.size()); + + } + + /** + * ***************************************************************** + *

+ * TEST CLASSES + */ + private static class DummyClass { + + public DummyClass() { + } + + @MyAnnotation + public void methodWithAnnotation() { + } + + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface MyAnnotation { + } + + private static final class DummyClassExt extends DummyClass { + + @MyAnnotation2 + public void anotherAnnotatedMethod() { + } + + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface MyAnnotation2 { + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Bar.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Bar.java new file mode 100644 index 000000000..fbddfab62 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Bar.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionSupport; + + +/** + * @author Pat Lightbody + * @author $Author$ + * @version $Revision$ + */ +public class Bar extends ActionSupport { + + Long id; + String title; + int somethingElse; + + + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return this.id; + } + + public void setSomethingElse(int somethingElse) { + this.somethingElse = somethingElse; + } + + public int getSomethingElse() { + return somethingElse; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + @Override + public String toString() { + return getTitle() + ":" + getSomethingElse(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/BarJunior.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/BarJunior.java new file mode 100644 index 000000000..5e563c753 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/BarJunior.java @@ -0,0 +1,4 @@ +package com.opensymphony.xwork2.util; + +public class BarJunior extends Bar { +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Cat.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Cat.java new file mode 100644 index 000000000..cfb1b50cc --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Cat.java @@ -0,0 +1,58 @@ +/* + * 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.util; + +import java.util.List; + + +/** + * @author Pat Lightbody + * @author $Author$ + * @version $Revision$ + */ +public class Cat { + + public static final String SCIENTIFIC_NAME = "Feline"; + + Foo foo; + List kittens; + String name; + + + public void setFoo(Foo foo) { + this.foo = foo; + } + + public Foo getFoo() { + return foo; + } + + public void setKittens(List kittens) { + this.kittens = kittens; + } + + public List getKittens() { + return kittens; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/ClassLoaderUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ClassLoaderUtilTest.java new file mode 100644 index 000000000..a179da9c2 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ClassLoaderUtilTest.java @@ -0,0 +1,124 @@ +/* + * 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.util; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; +import java.util.Arrays; +import java.util.Enumeration; + +public class ClassLoaderUtilTest extends TestCase { + + public void testGetResources() throws IOException { + Iterator i = ClassLoaderUtil.getResources("xwork-sample.xml", ClassLoaderUtilTest.class, false); + assertNotNull(i); + + assertTrue(i.hasNext()); + URL url = i.next(); + assertTrue(url.toString().endsWith("xwork-sample.xml")); + assertTrue(!i.hasNext()); + } + + public void testGetResources_Multiple() throws IOException { + Iterator i = ClassLoaderUtil.getResources("xwork-1.0.dtd", ClassLoaderUtilTest.class, false); + assertNotNull(i); + + assertTrue(i.hasNext()); + URL url = i.next(); + assertTrue(url.toString().endsWith("xwork-1.0.dtd")); + url = i.next(); + assertTrue(url.toString().endsWith("xwork-1.0.dtd")); + assertTrue(!i.hasNext()); + } + + public void testGetResources_Aggregate() throws IOException { + Iterator i = ClassLoaderUtil.getResources("xwork-1.0.dtd", ClassLoaderUtilTest.class, true); + assertNotNull(i); + + assertTrue(i.hasNext()); + URL url = i.next(); + assertTrue(url.toString().endsWith("xwork-1.0.dtd")); + url = i.next(); + assertTrue(url.toString().endsWith("xwork-1.0.dtd")); + assertTrue(!i.hasNext()); + } + + public void testGetResources_None() throws IOException { + Iterator i = ClassLoaderUtil.getResources("asdfasdf.html", ClassLoaderUtilTest.class, false); + assertNotNull(i); + + assertTrue(!i.hasNext()); + } + + public void testGetResource() { + URL url = ClassLoaderUtil.getResource("xwork-sample.xml", ClassLoaderUtilTest.class); + assertNotNull(url); + + assertTrue(url.toString().endsWith("xwork-sample.xml")); + } + + public void testGetResource_None() { + URL url = ClassLoaderUtil.getResource("asf.xml", ClassLoaderUtilTest.class); + assertNull(url); + } + + public void testAggregateIterator() { + ClassLoaderUtil.AggregateIterator aggr = new ClassLoaderUtil.AggregateIterator(); + + Enumeration en1 = new Enumeration() { + private Iterator itt = Arrays.asList("str1", "str1", "str3", "str1").iterator(); + public boolean hasMoreElements() { + return itt.hasNext(); + } + + public Object nextElement() { + return itt.next(); + } + }; + + Enumeration en2 = new Enumeration() { + private Iterator itt = Arrays.asList("str4", "str5").iterator(); + public boolean hasMoreElements() { + return itt.hasNext(); + } + + public Object nextElement() { + return itt.next(); + } + }; + + + aggr.addEnumeration(en1); + aggr.addEnumeration(en2); + + assertTrue(aggr.hasNext()); + assertEquals("str1", aggr.next()); + + assertTrue(aggr.hasNext()); + assertEquals("str3", aggr.next()); + + assertTrue(aggr.hasNext()); + assertEquals("str4", aggr.next()); + + assertTrue(aggr.hasNext()); + assertEquals("str5", aggr.next()); + + assertFalse(aggr.hasNext()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/ClassPathFinderTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ClassPathFinderTest.java new file mode 100644 index 000000000..616dcf315 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ClassPathFinderTest.java @@ -0,0 +1,54 @@ +/* + * $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.util; + +import com.opensymphony.xwork2.XWorkTestCase; + +import java.util.Vector; + +public class ClassPathFinderTest extends XWorkTestCase { + + public void testFinder() { + ClassPathFinder finder = new ClassPathFinder(); + finder.setPattern("**/xwork-test-wildcard-*.xml"); + Vector found = finder.findMatches(); + assertEquals(found.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml"), true ); + assertEquals(found.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-2.xml"), true ); + assertEquals(found.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-include.xml"), true ); + assertEquals(found.contains("com/opensymphony/xwork2/config/providers/xwork-test-results.xml"), false); + + ClassPathFinder finder2 = new ClassPathFinder(); + finder2.setPattern("com/*/xwork2/config/providers/xwork-test-wildcard-1.xml"); + Vector found2 = finder2.findMatches(); + assertEquals(found2.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml"), true); + assertEquals(found2.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-2.xml"), false); + + ClassPathFinder finder3 = new ClassPathFinder(); + finder3.setPattern("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml"); + Vector found3 = finder3.findMatches(); + assertEquals(found3.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml"), true); + assertEquals(found3.contains("com/opensymphony/xwork2/config/providers/xwork-test-wildcard-2.xml"), false); + + ClassPathFinder finder4 = new ClassPathFinder(); + finder4.setPattern("no/matches/*"); + Vector found4 = finder4.findMatches(); + assertEquals(found4.isEmpty(), true); + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Dog.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Dog.java new file mode 100644 index 000000000..8bad0474f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Dog.java @@ -0,0 +1,123 @@ +/* + * 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.util; + +import java.io.Serializable; + + +/** + * @author Pat Lightbody + * @author $Author$ + * @version $Revision$ + */ +public class Dog implements Serializable { + + public static final String SCIENTIFIC_NAME = "Canine"; + + + Cat hates; + String name; + int[] childAges; + boolean male; + int age; + static String deity; + + + public void setAge(int age) { + this.age = age; + } + + public int getAge() { + return age; + } + + public void setChildAges(int[] childAges) { + this.childAges = childAges; + } + + public int[] getChildAges() { + return childAges; + } + + public void setException(String blah) throws Exception { + throw new Exception("This is expected"); + } + + public String getException() throws Exception { + throw new Exception("This is expected"); + } + + public void setHates(Cat hates) { + this.hates = hates; + } + + public Cat getHates() { + return hates; + } + + public void setMale(boolean male) { + this.male = male; + } + + public boolean isMale() { + return male; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static String getDeity() { + return deity; + } + + public static void setDeity(String deity) { + Dog.deity = deity; + } + + public int computeDogYears() { + return age * 7; + } + + public int multiplyAge(int by) { + return age * by; + } + + /** + * @return null + */ + public Integer nullMethod() { + return null; + } + + /** + * a method which is safe to call with a null argument + * + * @param arg the Boolean to return + * @return arg, if it is not null, or Boolean.TRUE if arg is null + */ + public Boolean nullSafeMethod(Boolean arg) { + return (arg == null) ? Boolean.TRUE : arg; + } + + public void getBite() { + throw new RuntimeException("wuf wuf"); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java new file mode 100644 index 000000000..fa1af7928 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java @@ -0,0 +1,70 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.util.location.Location; +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import java.io.StringReader; + +/** + * Test cases for {@link DomHelper}. + */ +public class DomHelperTest extends TestCase { + + private String xml = "\n" + + "\n" + + "]>\n" + + "\n" + + " \n" + + "\n"; + + public void testParse() throws Exception { + InputSource in = new InputSource(new StringReader(xml)); + in.setSystemId("foo://bar"); + + Document doc = DomHelper.parse(in); + assertNotNull(doc); + assertTrue("Wrong root node", + "foo".equals(doc.getDocumentElement().getNodeName())); + + NodeList nl = doc.getElementsByTagName("bar"); + assertTrue(nl.getLength() == 1); + + + + } + + public void testGetLocationObject() throws Exception { + InputSource in = new InputSource(new StringReader(xml)); + in.setSystemId("foo://bar"); + + Document doc = DomHelper.parse(in); + + NodeList nl = doc.getElementsByTagName("bar"); + + Location loc = DomHelper.getLocationObject((Element)nl.item(0)); + + assertNotNull(loc); + assertTrue("Should be line 6, was "+loc.getLineNumber(), + 6==loc.getLineNumber()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/FileManagerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/FileManagerTest.java new file mode 100644 index 000000000..77472fd38 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/FileManagerTest.java @@ -0,0 +1,36 @@ +package com.opensymphony.xwork2.util; + +import com.opensymphony.xwork2.XWorkTestCase; + +import java.io.InputStream; +import java.net.URL; + +/** + * FileManager Tester. + * + * @author + * @since

02/18/2009
+ * @version 1.0 + */ +public class FileManagerTest extends XWorkTestCase { + + public void testGetFileInJar() throws Exception { + testLoadFile("xwork-jar.xml"); + testLoadFile("xwork - jar.xml"); + testLoadFile("xwork-zip.xml"); + testLoadFile("xwork - zip.xml"); + testLoadFile("xwork-jar2.xml"); + testLoadFile("xwork - jar2.xml"); + testLoadFile("xwork-zip2.xml"); + testLoadFile("xwork - zip2.xml"); + } + + private void testLoadFile(String fileName) { + FileManager.setReloadingConfigs(true); + URL url = ClassLoaderUtil.getResource(fileName, FileManagerTest.class); + InputStream file = FileManager.loadFile(url, true); + assertNotNull(file); + assertFalse(!FileManager.fileNeedsReloading(fileName)); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Foo.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Foo.java new file mode 100644 index 000000000..70c4fd38b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Foo.java @@ -0,0 +1,218 @@ +/* + * 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.util; + +import java.util.*; + + +/** + * @author Pat Lightbody + * @author $Author$ + * @version $Revision$ + */ +public class Foo { + + Bar bar; + Date birthday; + Date event; + Date meeting; + Foo child; + List cats; + List moreCats; + List strings; + Collection barCollection; + Map catMap; + Map anotherCatMap; + String title; + long[] points; + Foo[] relatives; + boolean useful; + int number; + long aLong; + Calendar calendar; + BarJunior barJunior; + + public BarJunior getBarJunior() { + return barJunior; + } + + public void setBarJunior(BarJunior barJunior) { + this.barJunior = barJunior; + } + + public void setALong(long aLong) { + this.aLong = aLong; + } + + public long getALong() { + return aLong; + } + + public void setBar(Bar bar) { + this.bar = bar; + } + + public Bar getBar() { + return bar; + } + + public void setBirthday(Date birthday) { + this.birthday = birthday; + } + + public Date getBirthday() { + return birthday; + } + + public void setCatMap(Map catMap) { + this.catMap = catMap; + } + + public Map getCatMap() { + return catMap; + } + + public void setCats(List cats) { + this.cats = cats; + } + + public List getCats() { + return cats; + } + + public void setChild(Foo child) { + this.child = child; + } + + public Foo getChild() { + return child; + } + + public void setNumber(int number) { + this.number = number; + } + + public int getNumber() { + return number; + } + + /** + * @return Returns the anotherCatMap. + */ + public Map getAnotherCatMap() { + return anotherCatMap; + } + + /** + * @param anotherCatMap The anotherCatMap to set. + */ + public void setAnotherCatMap(Map anotherCatMap) { + this.anotherCatMap = anotherCatMap; + } + + /** + * @return Returns the moreCats. + */ + public List getMoreCats() { + return moreCats; + } + + /** + * @param moreCats The moreCats to set. + */ + public void setMoreCats(List moreCats) { + this.moreCats = moreCats; + } + + /** + * @return Returns the catSet. + */ + public Collection getBarCollection() { + return barCollection; + } + + /** + * @param barCollection The barCollection to set. + */ + public void setBarCollection(Collection barCollection) { + this.barCollection = barCollection; + } + + public void setPoints(long[] points) { + this.points = points; + } + + public long[] getPoints() { + return points; + } + + public void setRelatives(Foo[] relatives) { + this.relatives = relatives; + } + + public Foo[] getRelatives() { + return relatives; + } + + public void setStrings(List strings) { + this.strings = strings; + } + + public List getStrings() { + return strings; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setUseful(boolean useful) { + this.useful = useful; + } + + public boolean isUseful() { + return useful; + } + + + public Date getEvent() { + return event; + } + + public void setEvent(Date event) { + this.event = event; + } + + public Date getMeeting() { + return meeting; + } + + public void setMeeting(Date meeting) { + this.meeting = meeting; + } + + public Calendar getCalendar() { + return calendar; + } + + public void setCalendar(Calendar calendar) { + this.calendar = calendar; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/FurColor.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/FurColor.java new file mode 100644 index 000000000..94c355bec --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/FurColor.java @@ -0,0 +1,20 @@ +/* + * 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.util; + +public enum FurColor { + BROWN, BLACK, GREEN +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/GetPropertiesTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/GetPropertiesTest.java new file mode 100644 index 000000000..6a24aca5b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/GetPropertiesTest.java @@ -0,0 +1,40 @@ +/* + * Created on Jan 23, 2006 + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package com.opensymphony.xwork2.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +/** + * @author Gabe + * + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +public class GetPropertiesTest extends XWorkTestCase { + + public void testGetCollectionProperties() { + doGetCollectionPropertiesTest(new ArrayList()); + doGetCollectionPropertiesTest(new HashSet()); + + } + + public void doGetCollectionPropertiesTest(Collection c) { + ValueStack vs = ActionContext.getContext().getValueStack(); + Foo foo = new Foo(); + foo.setBarCollection(c); + vs.push(foo); + assertEquals(Boolean.TRUE, vs.findValue("barCollection.isEmpty")); + assertEquals(Boolean.TRUE, vs.findValue("barCollection.empty")); + assertEquals(new Integer(0), vs.findValue("barCollection.size")); + assertTrue(vs.findValue("barCollection.iterator") instanceof java.util.Iterator); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Indexed.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Indexed.java new file mode 100644 index 000000000..70b2492b0 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Indexed.java @@ -0,0 +1,41 @@ +package com.opensymphony.xwork2.util; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author mimo + * + */ +public class Indexed { + + public Object[] values = new Object[3]; + public Map map = new HashMap(); + + public void setSimple(int i, Object v) { + values[i] = v; + } + + public Object getSimple(int i) { + return values[i]; + } + + + + public void setIntegerMap(String key, Integer value) { + map.put(key, value); + } + + public Integer getIntegerMap(String key) { + return (Integer) map.get(key); + } + + public void setStringMap(String key, String value) { + map.put(key, value); + } + + public String getStringMap(String key) { + return (String) map.get(key); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/ListHolder.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ListHolder.java new file mode 100644 index 000000000..190fdefca --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ListHolder.java @@ -0,0 +1,37 @@ +package com.opensymphony.xwork2.util; + +import java.util.Date; +import java.util.List; + +/** + * User: patrick Date: Dec 20, 2005 Time: 11:15:29 AM + */ +public class ListHolder { + List longs; + List strings; + List dates; + + public List getLongs() { + return longs; + } + + public void setLongs(List longs) { + this.longs = longs; + } + + public List getStrings() { + return strings; + } + + public void setStrings(List strings) { + this.strings = strings; + } + + public List getDates() { + return dates; + } + + public void setDates(List dates) { + this.dates = dates; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/LocalizedTextUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/LocalizedTextUtilTest.java new file mode 100644 index 000000000..0b3dd9e68 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/LocalizedTextUtilTest.java @@ -0,0 +1,260 @@ +/* + * 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.util; + +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.test.ModelDrivenAction2; +import com.opensymphony.xwork2.test.TestBean2; + +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.ResourceBundle; + + +/** + * Unit test for {@link LocalizedTextUtil}. + * + * @author jcarreira + * @author tm_jee + * + * @version $Date$ $Id$ + */ +public class LocalizedTextUtilTest extends XWorkTestCase { + + public void testNpeWhenClassIsPrimitive() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(new MyObject()); + String result = LocalizedTextUtil.findText(MyObject.class, "someObj.someI18nKey", Locale.ENGLISH, "default message", null, stack); + System.out.println(result); + } + + public static class MyObject extends ActionSupport { + public boolean getSomeObj() { + return true; + } + } + + public void testActionGetTextWithNullObject() throws Exception { + MyAction action = new MyAction(); + + Mock mockActionInvocation = new Mock(ActionInvocation.class); + mockActionInvocation.expectAndReturn("getAction", action); + ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy()); + ActionContext.getContext().getValueStack().push(action); + + String message = action.getText("barObj.title"); + assertEquals("Title:", message); + } + + + public static class MyAction extends ActionSupport { + private Bar testBean2; + + public Bar getBarObj() { + return testBean2; + } + public void setBarObj(Bar testBean2) { + this.testBean2 = testBean2; + } + } + + public void testActionGetText() throws Exception { + ModelDrivenAction2 action = new ModelDrivenAction2(); + TestBean2 bean = (TestBean2) action.getModel(); + Bar bar = new Bar(); + bean.setBarObj(bar); + + Mock mockActionInvocation = new Mock(ActionInvocation.class); + mockActionInvocation.expectAndReturn("getAction", action); + ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy()); + ActionContext.getContext().getValueStack().push(action); + ActionContext.getContext().getValueStack().push(action.getModel()); + + String message = action.getText("barObj.title"); + assertEquals("Title:", message); + } + + public void testNullKeys() { + LocalizedTextUtil.findText(this.getClass(), null, Locale.getDefault()); + } + + public void testActionGetTextXXX() throws Exception { + LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe"); + + SimpleAction action = new SimpleAction(); + + Mock mockActionInvocation = new Mock(ActionInvocation.class); + mockActionInvocation.expectAndReturn("getAction", action); + ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy()); + ActionContext.getContext().getValueStack().push(action); + + String message = action.getText("bean.name"); + String foundBean2 = action.getText("bean2.name"); + + assertEquals("Okay! You found Me!", foundBean2); + assertEquals("Haha you cant FindMe!", message); + } + + public void testAddDefaultResourceBundle() { + String text = LocalizedTextUtil.findDefaultText("foo.range", Locale.getDefault()); + assertNull("Found message when it should not be available.", null); + + LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction"); + + String message = LocalizedTextUtil.findDefaultText("foo.range", Locale.US); + assertEquals("Foo Range Message", message); + } + + public void testAddDefaultResourceBundle2() throws Exception { + LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction"); + + ActionProxy proxy = actionProxyFactory.createActionProxy("/", "packagelessAction", new HashMap(), false, true); + proxy.execute(); + } + + public void testDefaultMessage() throws Exception { + String message = LocalizedTextUtil.findDefaultText(XWorkMessages.ACTION_EXECUTION_ERROR, Locale.getDefault()); + assertEquals("Error during Action invocation", message); + } + + public void testDefaultMessageOverride() throws Exception { + String message = LocalizedTextUtil.findDefaultText(XWorkMessages.ACTION_EXECUTION_ERROR, Locale.getDefault()); + assertEquals("Error during Action invocation", message); + + LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/xwork2/test"); + + message = LocalizedTextUtil.findDefaultText(XWorkMessages.ACTION_EXECUTION_ERROR, Locale.getDefault()); + assertEquals("Testing resource bundle override", message); + } + + public void testFindTextInChildProperty() throws Exception { + ModelDriven action = new ModelDrivenAction2(); + TestBean2 bean = (TestBean2) action.getModel(); + Bar bar = new Bar(); + bean.setBarObj(bar); + + Mock mockActionInvocation = new Mock(ActionInvocation.class); + mockActionInvocation.expectAndReturn("hashCode", 0); + mockActionInvocation.expectAndReturn("getAction", action); + ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy()); + ActionContext.getContext().getValueStack().push(action); + ActionContext.getContext().getValueStack().push(action.getModel()); + + String message = LocalizedTextUtil.findText(ModelDrivenAction2.class, "invalid.fieldvalue.barObj.title", Locale.getDefault()); + assertEquals("Title is invalid!", message); + } + + public void testFindTextInInterface() throws Exception { + Action action = new ModelDrivenAction2(); + Mock mockActionInvocation = new Mock(ActionInvocation.class); + mockActionInvocation.expectAndReturn("getAction", action); + ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy()); + + String message = LocalizedTextUtil.findText(ModelDrivenAction2.class, "test.foo", Locale.getDefault()); + assertEquals("Foo!", message); + } + + public void testFindTextInPackage() throws Exception { + ModelDriven action = new ModelDrivenAction2(); + + Mock mockActionInvocation = new Mock(ActionInvocation.class); + mockActionInvocation.expectAndReturn("getAction", action); + ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy()); + + String message = LocalizedTextUtil.findText(ModelDrivenAction2.class, "package.properties", Locale.getDefault()); + assertEquals("It works!", message); + } + + public void testParameterizedDefaultMessage() throws Exception { + String message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{"AddUser"}); + assertEquals("There is no Action mapped for action name AddUser.", message); + } + + public void testParameterizedDefaultMessageWithPackage() throws Exception { + String message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{"blah", "AddUser"}); + assertEquals("There is no Action mapped for namespace blah and action name AddUser.", message); + } + + public void testLocalizedDateFormatIsUsed() { + LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest"); + Object[] params = new Object[]{new Date()}; + String usDate = LocalizedTextUtil.findDefaultText("test.format.date", Locale.US, params); + String germanDate = LocalizedTextUtil.findDefaultText("test.format.date", Locale.GERMANY, params); + assertFalse(usDate.equals(germanDate)); + } + + public void testXW377() { + LocalizedTextUtil.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest"); + + String text = LocalizedTextUtil.findText(Bar.class, "xw377", ActionContext.getContext().getLocale(), "xw377", null, ActionContext.getContext().getValueStack()); + assertEquals("xw377", text); // should not log + + String text2 = LocalizedTextUtil.findText(LocalizedTextUtilTest.class, "notinbundle", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack()); + assertEquals("hello", text2); // should log WARN + + String text3 = LocalizedTextUtil.findText(LocalizedTextUtilTest.class, "notinbundle.key", ActionContext.getContext().getLocale(), "notinbundle.key", null, ActionContext.getContext().getValueStack()); + assertEquals("notinbundle.key", text3); // should log WARN + + String text4 = LocalizedTextUtil.findText(LocalizedTextUtilTest.class, "xw377", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack()); + assertEquals("xw377", text4); // should not log + + String text5 = LocalizedTextUtil.findText(LocalizedTextUtilTest.class, "username", ActionContext.getContext().getLocale(), null, null, ActionContext.getContext().getValueStack()); + assertEquals("Santa", text5); // should not log + } + + public void testXW404() { + // This tests will try to load bundles from the 3 locales but we only have files for France and Germany. + // Before this fix loading the bundle for Germany failed since Italy have previously failed and thus the misses cache + // contained a false entry + + // Set default Locale to Locale.US + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + ResourceBundle rbFrance = LocalizedTextUtil.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.FRANCE); + ResourceBundle rbItaly = LocalizedTextUtil.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.ITALY); + ResourceBundle rbGermany = LocalizedTextUtil.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.GERMANY); + + // Reset to previous default Locale + Locale.setDefault(defaultLocale); + + assertNotNull(rbFrance); + assertEquals("Bonjour", rbFrance.getString("hello")); + + assertNull(rbItaly); + + assertNotNull(rbGermany); + assertEquals("Hallo", rbGermany.getString("hello")); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + + ActionContext.getContext().setLocale(Locale.US); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + LocalizedTextUtil.clearDefaultResourceBundles(); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBean.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBean.java new file mode 100644 index 000000000..eb456fb9c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBean.java @@ -0,0 +1,54 @@ +/* + * 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.util; + +import java.io.Serializable; + +/** + * MyBean + * + * @author Rainer Hermanns + */ +public class MyBean implements Serializable { + + private Long id; + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + @Override + public String toString() { + return "MyBean{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBeanAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBeanAction.java new file mode 100644 index 000000000..96fd1e011 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBeanAction.java @@ -0,0 +1,55 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.Action; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * MyBeanAction + * + * @author Rainer Hermanns + */ +public class MyBeanAction implements Action { + + private List beanList = new ArrayList(); + private Map beanMap = new HashMap(); + + public List getBeanList() { + return beanList; + } + + public void setBeanList(List beanList) { + this.beanList = beanList; + } + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public String execute() throws Exception { + return SUCCESS; + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBeanActionTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBeanActionTest.java new file mode 100644 index 000000000..d315ad317 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/MyBeanActionTest.java @@ -0,0 +1,102 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; +import java.util.Map; + +/** + * MyBeanActionTest + * + * @author Rainer Hermanns + */ +public class MyBeanActionTest extends XWorkTestCase { + + public void testIndexedList() { + HashMap params = new HashMap(); + params.put("beanList(1234567890).name", "This is the bla bean"); + params.put("beanList(1234567891).name", "This is the 2nd bla bean"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "MyBean", extraContext); + proxy.execute(); + assertEquals(2, Integer.parseInt(proxy.getInvocation().getStack().findValue("beanList.size").toString())); + assertEquals(MyBean.class.getName(), proxy.getInvocation().getStack().findValue("beanList.get(0)").getClass().getName()); + assertEquals(MyBean.class.getName(), proxy.getInvocation().getStack().findValue("beanList.get(1)").getClass().getName()); + + assertEquals("This is the bla bean", proxy.getInvocation().getStack().findValue("beanList.get(0).name")); + assertEquals(new Long(1234567890), Long.valueOf(proxy.getInvocation().getStack().findValue("beanList.get(0).id").toString())); + assertEquals("This is the 2nd bla bean", proxy.getInvocation().getStack().findValue("beanList.get(1).name")); + assertEquals(new Long(1234567891), Long.valueOf(proxy.getInvocation().getStack().findValue("beanList.get(1).id").toString())); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testIndexedMap() { + HashMap params = new HashMap(); + params.put("beanMap[1234567890].id", "1234567890"); + params.put("beanMap[1234567891].id", "1234567891"); + + params.put("beanMap[1234567890].name", "This is the bla bean"); + params.put("beanMap[1234567891].name", "This is the 2nd bla bean"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "MyBean", extraContext); + proxy.execute(); + MyBeanAction action = (MyBeanAction) proxy.getInvocation().getAction(); + + assertEquals(2, Integer.parseInt(proxy.getInvocation().getStack().findValue("beanMap.size").toString())); + + Map map = (Map) proxy.getInvocation().getStack().findValue("beanMap"); + assertEquals(true, action.getBeanMap().containsKey(new Long(1234567890))); + assertEquals(true, action.getBeanMap().containsKey(new Long(1234567891))); + + + assertEquals(MyBean.class.getName(), proxy.getInvocation().getStack().findValue("beanMap.get(1234567890L)").getClass().getName()); + assertEquals(MyBean.class.getName(), proxy.getInvocation().getStack().findValue("beanMap.get(1234567891L)").getClass().getName()); + + assertEquals("This is the bla bean", proxy.getInvocation().getStack().findValue("beanMap.get(1234567890L).name")); + assertEquals("This is the 2nd bla bean", proxy.getInvocation().getStack().findValue("beanMap.get(1234567891L).name")); + + assertEquals("1234567890", proxy.getInvocation().getStack().findValue("beanMap.get(1234567890L).id").toString()); + assertEquals("1234567891", proxy.getInvocation().getStack().findValue("beanMap.get(1234567891L).id").toString()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/NamedVariablePatternMatcherTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/NamedVariablePatternMatcherTest.java new file mode 100644 index 000000000..613712559 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/NamedVariablePatternMatcherTest.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.util; + +import com.opensymphony.xwork2.util.NamedVariablePatternMatcher.CompiledPattern; +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NamedVariablePatternMatcherTest extends TestCase { + + public void testCompile() { + NamedVariablePatternMatcher matcher = new NamedVariablePatternMatcher(); + + assertNull(matcher.compilePattern(null)); + assertNull(matcher.compilePattern("")); + + CompiledPattern pattern = matcher.compilePattern("foo"); + assertEquals("foo", pattern.getPattern().pattern()); + + pattern = matcher.compilePattern("foo{jim}"); + assertEquals("foo([^/]+)", pattern.getPattern().pattern()); + assertEquals("jim", pattern.getVariableNames().get(0)); + + pattern = matcher.compilePattern("foo{jim}/{bob}"); + assertEquals("foo([^/]+)/([^/]+)", pattern.getPattern().pattern()); + assertEquals("jim", pattern.getVariableNames().get(0)); + assertEquals("bob", pattern.getVariableNames().get(1)); + assertTrue(pattern.getPattern().matcher("foostar/jie").matches()); + assertFalse(pattern.getPattern().matcher("foo/star/jie").matches()); + } + + public void testMatch() { + NamedVariablePatternMatcher matcher = new NamedVariablePatternMatcher(); + + Map vars = new HashMap(); + CompiledPattern pattern = new CompiledPattern(Pattern.compile("foo([^/]+)"), Arrays.asList("bar")); + + assertTrue(matcher.match(vars, "foobaz", pattern)); + assertEquals("baz", vars.get("bar")); + } + + public void testIsLiteral() { + NamedVariablePatternMatcher matcher = new NamedVariablePatternMatcher(); + + assertTrue(matcher.isLiteral("bob")); + assertFalse(matcher.isLiteral("bob{jim}")); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Owner.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Owner.java new file mode 100644 index 000000000..450e215c5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Owner.java @@ -0,0 +1,37 @@ +/* + * 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.util; + + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class Owner { + + private Dog dog; + + + public void setDog(Dog dog) { + this.dog = dog; + } + + public Dog getDog() { + return dog; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/ResolverUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ResolverUtilTest.java new file mode 100644 index 000000000..be9774ffa --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/ResolverUtilTest.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.spring.SpringObjectFactory; +import junit.framework.TestCase; + +import java.net.URL; +import java.util.Set; + +public class ResolverUtilTest extends TestCase { + + public void testSimpleFind() throws Exception { + ResolverUtil resolver = new ResolverUtil(); + resolver.findImplementations(ObjectFactory.class, "com"); + Set> impls = resolver.getClasses(); + + assertTrue(impls.contains(ObjectFactory.class)); + assertTrue(impls.contains(SpringObjectFactory.class)); + } + + public void testMissingSomeFind() throws Exception { + ResolverUtil resolver = new ResolverUtil(); + resolver.findImplementations(ObjectFactory.class, "com.opensymphony.xwork2.spring"); + Set> impls = resolver.getClasses(); + + assertFalse(impls.contains(ObjectFactory.class)); + assertTrue(impls.contains(SpringObjectFactory.class)); + } + + public void testFindNamedResource() throws Exception { + ResolverUtil resolver = new ResolverUtil(); + resolver.findNamedResource("xwork-default.xml", ""); + Set impls = resolver.getResources(); + + assertTrue(impls.size() > 0); + } + + public void testFindNamedResourceInDir() throws Exception { + ResolverUtil resolver = new ResolverUtil(); + resolver.findNamedResource("SimpleAction.properties", "com/opensymphony"); + Set impls = resolver.getResources(); + + assertTrue(impls.size() > 0); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java new file mode 100644 index 000000000..d1d15b222 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java @@ -0,0 +1,141 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; + +import java.util.*; + +/** + * Unit test of {@link TextParseUtil}. + * + * @author plightbo + * @author tm_jee + * + * @version $Date$ $Id$ + */ +public class TextParseUtilTest extends XWorkTestCase { + + + public void testTranslateVariablesWithEvaluator() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(new Object() { + public String getMyVariable() { + return "My Variable "; + } + }); + + TextParseUtil.ParsedValueEvaluator evaluator = new TextParseUtil.ParsedValueEvaluator() { + public Object evaluate(Object parsedValue) { + return parsedValue.toString()+"Something"; + } + }; + + String result = TextParseUtil.translateVariables("Hello ${myVariable}", stack, evaluator); + + assertEquals(result, "Hello My Variable Something"); + } + + public void testTranslateVariables() { + ValueStack stack = ActionContext.getContext().getValueStack(); + + Object s = TextParseUtil.translateVariables("foo: ${{1, 2, 3}}, bar: ${1}", stack); + assertEquals("foo: [1, 2, 3], bar: 1", s); + + s = TextParseUtil.translateVariables("foo: %{{1, 2, 3}}, bar: %{1}", stack); + assertEquals("foo: [1, 2, 3], bar: 1", s); + + s = TextParseUtil.translateVariables("foo: %{{1, 2, 3}}, bar: %{1}", stack); + assertEquals("foo: [1, 2, 3], bar: 1", s); + + s = TextParseUtil.translateVariables("foo: ${#{1 : 2, 3 : 4}}, bar: ${1}", stack); + assertEquals("foo: {1=2, 3=4}, bar: 1", s); + + s = TextParseUtil.translateVariables("foo: %{#{1 : 2, 3 : 4}}, bar: %{1}", stack); + assertEquals("foo: {1=2, 3=4}, bar: 1", s); + + s = TextParseUtil.translateVariables("foo: 1}", stack); + assertEquals("foo: 1}", s); + + s = TextParseUtil.translateVariables("foo: {1}", stack); + assertEquals("foo: {1}", s); + + s = TextParseUtil.translateVariables("foo: ${1", stack); + assertEquals("foo: ${1", s); + + s = TextParseUtil.translateVariables("foo: %{1", stack); + assertEquals("foo: %{1", s); + + s = TextParseUtil.translateVariables('$', "${{1, 2, 3}}", stack, Object.class); + assertNotNull(s); + assertTrue("List not returned when parsing a 'pure' list", s instanceof List); + assertEquals(((List)s).size(), 3); + + s = TextParseUtil.translateVariables('$', "${#{'key1':'value1','key2':'value2','key3':'value3'}}", stack, Object.class); + assertNotNull(s); + assertTrue("Map not returned when parsing a 'pure' map", s instanceof Map); + assertEquals(((Map)s).size(), 3); + + s = TextParseUtil.translateVariables('$', "${1} two ${3}", stack, Object.class); + assertEquals("1 two 3", s); + + s = TextParseUtil.translateVariables('$', "count must be between ${123} and ${456}, current value is ${98765}.", stack, Object.class); + assertEquals("count must be between 123 and 456, current value is 98765.", s); + } + + public void testCommaDelimitedStringToSet() { + assertEquals(0, TextParseUtil.commaDelimitedStringToSet("").size()); + assertEquals(new HashSet(Arrays.asList("foo", "bar", "tee")), + TextParseUtil.commaDelimitedStringToSet(" foo, bar,tee")); + } + + public void testTranslateVariablesOpenChar() { + // just a quick test to see if the open char works + // most test are done the methods above + ValueStack stack = ActionContext.getContext().getValueStack(); + + Object s = TextParseUtil.translateVariables('$', "foo: ${{1, 2, 3}}, bar: ${1}", stack); + assertEquals("foo: [1, 2, 3], bar: 1", s); + + Object s2 = TextParseUtil.translateVariables('#', "foo: #{{1, 2, 3}}, bar: #{1}", stack); + assertEquals("foo: [1, 2, 3], bar: 1", s2); + } + + public void testTranslateNoVariables() { + ValueStack stack = ActionContext.getContext().getValueStack(); + + Object s = TextParseUtil.translateVariables('$', "foo: ${}", stack); + assertEquals("foo: ", s); + } + + public void testTranslateVariablesNoRecursive() { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(new HashMap() {{ put("foo", "${1+1}"); }}); + + Object s = TextParseUtil.translateVariables('$', "foo: ${foo}", stack, String.class, null, 1); + assertEquals("foo: ${1+1}", s); + } + + public void testTranslateVariablesRecursive() { + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(new HashMap() {{ put("foo", "${1+1}"); }}); + + Object s = TextParseUtil.translateVariables('$', "foo: ${foo}", stack, String.class, null, 2); + assertEquals("foo: 2", s); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/Tiger.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Tiger.java new file mode 100644 index 000000000..2be778328 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/Tiger.java @@ -0,0 +1,39 @@ +/* + * 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.util; + +import java.util.List; + + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class Tiger extends Cat { + + List dogs; + + + public void setDogs(List dogs) { + this.dogs = dogs; + } + + public List getDogs() { + return dogs; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/URLUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/URLUtilTest.java new file mode 100644 index 000000000..68455be7b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/URLUtilTest.java @@ -0,0 +1,153 @@ +package com.opensymphony.xwork2.util; + +import junit.framework.TestCase; + +import java.net.URL; +import java.net.MalformedURLException; +import java.net.URLStreamHandlerFactory; +import java.net.URLStreamHandler; +import java.net.URLConnection; +import java.io.IOException; + +public class URLUtilTest extends TestCase { + + public void testSimpleFile() throws MalformedURLException { + URL url = new URL("file:c:/somefile.txt"); + URL outputURL = URLUtil.normalizeToFileProtocol(url); + + assertNull(outputURL); + } + + public void testJarFile() throws MalformedURLException { + URL url = new URL("jar:file:/c:/somefile.jar!/"); + URL outputURL = URLUtil.normalizeToFileProtocol(url); + + assertNotNull(outputURL); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("jar:file:/c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("jar:file:c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:c:/somefile.jar", outputURL.toExternalForm()); + } + + public void testZipFile() throws MalformedURLException { + URL url = new URL("zip:/c:/somefile.zip!/"); + URL outputURL = URLUtil.normalizeToFileProtocol(url); + + assertNotNull(outputURL); + assertEquals("file:/c:/somefile.zip", outputURL.toExternalForm()); + + url = new URL("zip:/c:/somefile.zip!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.zip", outputURL.toExternalForm()); + + url = new URL("zip:c:/somefile.zip!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:c:/somefile.zip", outputURL.toExternalForm()); + } + + public void testWSJarFile() throws MalformedURLException { + URL url = new URL("wsjar:file:/c:/somefile.jar!/"); + URL outputURL = URLUtil.normalizeToFileProtocol(url); + + assertNotNull(outputURL); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("wsjar:file:/c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("wsjar:file:c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:c:/somefile.jar", outputURL.toExternalForm()); + } + + public void testVsFile() throws MalformedURLException { + URL url = new URL("vfsfile:/c:/somefile.jar!/"); + URL outputURL = URLUtil.normalizeToFileProtocol(url); + + assertNotNull(outputURL); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("vfsfile:/c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("vfsfile:c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("vfszip:/c:/somefile.war/somelibrary.jar"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.war/somelibrary.jar", outputURL.toExternalForm()); + } + + public void testJBossFile() throws MalformedURLException { + URL url = new URL("vfszip:/c:/somefile.jar!/"); + URL outputURL = URLUtil.normalizeToFileProtocol(url); + + assertNotNull(outputURL); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("vfszip:/c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("vfsmemory:c:/somefile.jar!/somestuf/bla/bla"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:c:/somefile.jar", outputURL.toExternalForm()); + + url = new URL("vfsmemory:/c:/somefile.war/somelibrary.jar"); + outputURL = URLUtil.normalizeToFileProtocol(url); + assertEquals("file:/c:/somefile.war/somelibrary.jar", outputURL.toExternalForm()); + } + + protected void setUp() throws Exception { + super.setUp(); + + try { + URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() { + public URLStreamHandler createURLStreamHandler(String protocol) { + return new URLStreamHandler() { + protected URLConnection openConnection(URL u) throws IOException { + return null; + } + }; + } + }); + } catch (Throwable e) { + //the factory cant be set multiple times..just ignore exception no biggie + } + } + + public void testVerifyUrl() { + assertEquals(false, URLUtil.verifyUrl(null)); + assertEquals(false, URLUtil.verifyUrl("")); + assertEquals(false, URLUtil.verifyUrl(" ")); + assertEquals(false, URLUtil.verifyUrl("no url")); + + assertEquals(true, URLUtil.verifyUrl("http://www.opensymphony.com")); + assertEquals(true, URLUtil.verifyUrl("https://www.opensymphony.com")); + assertEquals(true, URLUtil.verifyUrl("https://www.opensymphony.com:443/login")); + assertEquals(true, URLUtil.verifyUrl("http://localhost:8080/myapp")); + } + + public void testIsJarURL() throws Exception { + assertTrue(URLUtil.isJarURL(new URL("jar:file:/c:/somelibrary.jar!/com/opensymphony"))); + assertTrue(URLUtil.isJarURL(new URL("zip:/c:/somelibrary.jar!/com/opensymphony"))); + assertTrue(URLUtil.isJarURL(new URL("wsjar:/c:/somelibrary.jar!/com/opensymphony"))); + assertTrue(URLUtil.isJarURL(new URL("vfsfile:/c:/somelibrary.jar!/com/opensymphony"))); + assertTrue(URLUtil.isJarURL(new URL("vfszip:/c:/somelibrary.jar/com/opensymphony"))); + } + + public void testIsJBoss5Url() throws Exception { + assertTrue(URLUtil.isJBoss5Url(new URL("vfszip:/c:/somewar.war/somelibrary.jar"))); + assertFalse(URLUtil.isJBoss5Url(new URL("vfsfile:/c:/somewar.war/somelibrary.jar"))); + assertFalse(URLUtil.isJBoss5Url(new URL("jar:file:/c:/somelibrary.jar"))); + assertTrue(URLUtil.isJBoss5Url(new URL("vfsmemory:/c:/somewar.war/somelibrary.jar"))); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/UnknownHandlerManagerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/UnknownHandlerManagerTest.java new file mode 100644 index 000000000..9533eb0a3 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/UnknownHandlerManagerTest.java @@ -0,0 +1,82 @@ +package com.opensymphony.xwork2.util; + +import java.util.List; + +import com.opensymphony.xwork2.UnknownHandler; +import com.opensymphony.xwork2.UnknownHandlerManager; +import com.opensymphony.xwork2.UnknownHandlerManagerMock; +import com.opensymphony.xwork2.DefaultUnknownHandlerManager; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.providers.ConfigurationTestBase; +import com.opensymphony.xwork2.config.providers.SomeUnknownHandler; + +/** + * Test UnknownHandlerUtil + */ +public class UnknownHandlerManagerTest extends ConfigurationTestBase { + + public void testStack() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + loadConfigurationProviders(provider); + configurationManager.reload(); + + UnknownHandlerManager unknownHandlerManager = new DefaultUnknownHandlerManager(); + container.inject(unknownHandlerManager); + List unknownHandlers = unknownHandlerManager.getUnknownHandlers(); + + assertNotNull(unknownHandlers); + assertEquals(2, unknownHandlers.size()); + + UnknownHandler uh1 = unknownHandlers.get(0); + UnknownHandler uh2 = unknownHandlers.get(1); + + assertTrue(uh1 instanceof SomeUnknownHandler); + assertTrue(uh2 instanceof SomeUnknownHandler); + } + + public void testEmptyStack() throws ConfigurationException { + final String filename = "com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack-empty.xml"; + ConfigurationProvider provider = buildConfigurationProvider(filename); + loadConfigurationProviders(provider); + configurationManager.reload(); + + UnknownHandlerManager unknownHandlerManager = new DefaultUnknownHandlerManager(); + container.inject(unknownHandlerManager); + List unknownHandlers = unknownHandlerManager.getUnknownHandlers(); + + assertNotNull(unknownHandlers); + assertEquals(2, unknownHandlers.size()); + + UnknownHandler uh1 = unknownHandlers.get(0); + UnknownHandler uh2 = unknownHandlers.get(1); + + assertTrue(uh1 instanceof SomeUnknownHandler); + assertTrue(uh2 instanceof SomeUnknownHandler); + } + + public void testInvocationOrder() throws ConfigurationException, NoSuchMethodException { + SomeUnknownHandler uh1 = new SomeUnknownHandler(); + uh1.setActionMethodResult("uh1"); + + SomeUnknownHandler uh2 = new SomeUnknownHandler(); + uh2.setActionMethodResult("uh2"); + + UnknownHandlerManagerMock uhm = new UnknownHandlerManagerMock(); + uhm.addUnknownHandler(uh1); + uhm.addUnknownHandler(uh2); + + //should pick the first one + assertEquals("uh1", uhm.handleUnknownMethod(null, null)); + + //should pick the second one + uh1.setActionMethodResult(null); + assertEquals("uh2", uhm.handleUnknownMethod(null, null)); + + //should not pick any + uh1.setActionMethodResult(null); + uh2.setActionMethodResult(null); + assertEquals(null, uhm.handleUnknownMethod(null, null)); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/UrlUtilTest2.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/UrlUtilTest2.java new file mode 100644 index 000000000..087a17c5b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/UrlUtilTest2.java @@ -0,0 +1,35 @@ +package com.opensymphony.xwork2.util; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.jar.JarInputStream; + +/** + * Keep these test on a separate class, they can't be in UrlUtilTest because the + * registered URLStreamHandlerFactory would make them fail + */ +public class UrlUtilTest2 extends TestCase { + public void testOpenWithJarProtocol() throws IOException { + URL url = ClassLoaderUtil.getResource("xwork-jar.jar", URLUtil.class); + URL jarUrl = new URL("jar", "", url.toExternalForm() + "!/"); + URL outputURL = URLUtil.normalizeToFileProtocol(jarUrl); + assertNotNull(outputURL); + assertUrlCanBeOpened(outputURL); + } + + private void assertUrlCanBeOpened(URL url) throws IOException { + InputStream is = url.openStream(); + JarInputStream jarStream = null; + try { + jarStream = new JarInputStream(is); + assertNotNull(jarStream); + } finally { + if (jarStream != null) + jarStream.close(); + + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/WildcardHelperTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/WildcardHelperTest.java new file mode 100644 index 000000000..16d3a7dab --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/WildcardHelperTest.java @@ -0,0 +1,56 @@ +/* + * $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.util; + +import com.opensymphony.xwork2.XWorkTestCase; + +import java.util.HashMap; + +public class WildcardHelperTest extends XWorkTestCase { + + public void testMatch() { + + WildcardHelper wild = new WildcardHelper(); + HashMap matchedPatterns = new HashMap(); + int[] pattern = wild.compilePattern("wes-rules"); + assertEquals(wild.match(matchedPatterns,"wes-rules", pattern), true); + assertEquals(wild.match(matchedPatterns, "rules-wes", pattern), false); + + pattern = wild.compilePattern("wes-*"); + assertEquals(wild.match(matchedPatterns,"wes-rules", pattern), true); + assertEquals("rules".equals(matchedPatterns.get("1")), true); + assertEquals(wild.match(matchedPatterns, "rules-wes", pattern), false); + + pattern = wild.compilePattern("path/**/file"); + assertEquals(wild.match(matchedPatterns, "path/to/file", pattern), true); + assertEquals("to".equals(matchedPatterns.get("1")), true); + assertEquals(wild.match(matchedPatterns, "path/to/another/location/of/file", pattern), true); + assertEquals("to/another/location/of".equals(matchedPatterns.get("1")), true); + + pattern = wild.compilePattern("path/*/file"); + assertEquals(wild.match(matchedPatterns, "path/to/file", pattern), true); + assertEquals("to".equals(matchedPatterns.get("1")), true); + assertEquals(wild.match(matchedPatterns, "path/to/another/location/of/file", pattern), false); + + pattern = wild.compilePattern("path/*/another/**/file"); + assertEquals(wild.match(matchedPatterns, "path/to/another/location/of/file", pattern), true); + assertEquals("to".equals(matchedPatterns.get("1")), true); + assertEquals("location/of".equals(matchedPatterns.get("2")), true); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/XWorkListTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/XWorkListTest.java new file mode 100644 index 000000000..0e1fd59a1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/XWorkListTest.java @@ -0,0 +1,90 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; + +import java.util.ArrayList; + + +/** + * Test cases for {@link XWorkList}. + * + * @author Mark Woon + */ +public class XWorkListTest extends XWorkTestCase { + + public void testAddAllIndex() { + XWorkConverter conv = container.getInstance(XWorkConverter.class); + ObjectFactory of = container.getInstance(ObjectFactory.class); + XWorkList xworkList = new XWorkList(of, conv, String.class); + xworkList.add(new String[]{"a"}); + xworkList.add("b"); + + ArrayList addList = new ArrayList(); + addList.add(new String[]{"1"}); + addList.add(new String[]{"2"}); + addList.add(new String[]{"3"}); + + // trim + xworkList.addAll(3, addList); + assertEquals(6, xworkList.size()); + assertEquals("a", xworkList.get(0)); + assertEquals("b", xworkList.get(1)); + assertEquals("", xworkList.get(2)); + assertEquals("1", xworkList.get(3)); + assertEquals("2", xworkList.get(4)); + assertEquals("3", xworkList.get(5)); + + // take 2, no trim + xworkList = new XWorkList(of, conv,String.class); + xworkList.add(new String[]{"a"}); + xworkList.add("b"); + + addList = new ArrayList(); + addList.add(new String[]{"1"}); + addList.add(new String[]{"2"}); + addList.add(new String[]{"3"}); + + xworkList.addAll(2, addList); + assertEquals(5, xworkList.size()); + assertEquals("a", xworkList.get(0)); + assertEquals("b", xworkList.get(1)); + assertEquals("1", xworkList.get(2)); + assertEquals("2", xworkList.get(3)); + assertEquals("3", xworkList.get(4)); + + // take 3, insert + xworkList = new XWorkList(of, conv,String.class); + xworkList.add(new String[]{"a"}); + xworkList.add("b"); + + addList = new ArrayList(); + addList.add(new String[]{"1"}); + addList.add(new String[]{"2"}); + addList.add(new String[]{"3"}); + + xworkList.addAll(1, addList); + assertEquals(5, xworkList.size()); + assertEquals("a", xworkList.get(0)); + assertEquals("1", xworkList.get(1)); + assertEquals("2", xworkList.get(2)); + assertEquals("3", xworkList.get(3)); + assertEquals("b", xworkList.get(4)); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationAttributesTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationAttributesTest.java new file mode 100644 index 000000000..416e16c92 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationAttributesTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 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.util.location; + +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.Locator; +import org.xml.sax.helpers.AttributesImpl; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +public class LocationAttributesTest extends TestCase { + + public LocationAttributesTest(String name) { + super(name); + } + + public void testAddLocationAttributes() throws Exception { + AttributesImpl attrs = new AttributesImpl(); + LocationAttributes.addLocationAttributes(new Locator() { + public int getColumnNumber() { return 40; } + public int getLineNumber() { return 1; } + public String getSystemId() { return "path/to/file.xml"; } + public String getPublicId() { return "path/to/file.xml"; } + }, attrs); + + assertTrue("path/to/file.xml".equals(attrs.getValue("loc:src"))); + assertTrue("1".equals(attrs.getValue("loc:line"))); + assertTrue("40".equals(attrs.getValue("loc:column"))); + } + + public void testRecursiveRemove() throws Exception { + Document doc = getDoc("xml-with-location.xml"); + + Element root = doc.getDocumentElement(); + LocationAttributes.remove(root, true); + + assertNull(root.getAttributeNode("loc:line")); + assertNull(root.getAttributeNode("loc:column")); + assertNull(root.getAttributeNode("loc:src")); + + Element kid = (Element)doc.getElementsByTagName("bar").item(0); + assertNull(kid.getAttributeNode("loc:line")); + assertNull(kid.getAttributeNode("loc:column")); + assertNull(kid.getAttributeNode("loc:src")); + } + + public void testNonRecursiveRemove() throws Exception { + Document doc = getDoc("xml-with-location.xml"); + + Element root = doc.getDocumentElement(); + LocationAttributes.remove(root, false); + + assertNull(root.getAttributeNode("loc:line")); + assertNull(root.getAttributeNode("loc:column")); + assertNull(root.getAttributeNode("loc:src")); + + Element kid = (Element)doc.getElementsByTagName("bar").item(0); + assertNotNull(kid.getAttributeNode("loc:line")); + assertNotNull(kid.getAttributeNode("loc:column")); + assertNotNull(kid.getAttributeNode("loc:src")); + } + + private Document getDoc(String path) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(LocationAttributesTest.class.getResourceAsStream(path)); + + + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationImplTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationImplTest.java new file mode 100644 index 000000000..54b4dd407 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationImplTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2005 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.util.location; + +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URL; +import java.util.List; + +public class LocationImplTest extends TestCase { + + public LocationImplTest(String name) { + super(name); + } + + static final String str = "path/to/file.xml:1:40"; + + public void testEquals() throws Exception { + Location loc1 = LocationUtils.parse(str); + Location loc2 = new LocationImpl(null, "path/to/file.xml", 1, 40); + + assertEquals("locations", loc1, loc2); + assertEquals("hashcode", loc1.hashCode(), loc2.hashCode()); + assertEquals("string representation", loc1.toString(), loc2.toString()); + } + + /** + * Test that Location.UNKNOWN is kept identical on deserialization + */ + public void testSerializeUnknown() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + + oos.writeObject(Location.UNKNOWN); + oos.close(); + bos.close(); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + + Object obj = ois.readObject(); + + assertSame("unknown location", Location.UNKNOWN, obj); + } + + public void testGetSnippet() throws Exception { + URL url = ClassLoaderUtil.getResource("com/opensymphony/xwork2/somefile.txt", getClass()); + Location loc = new LocationImpl("foo", url.toString(), 3, 2); + + List snippet = loc.getSnippet(1); + assertNotNull(snippet); + assertTrue("Wrong length: "+snippet.size(), 3 == snippet.size()); + + assertTrue("is".equals(snippet.get(0))); + assertTrue("a".equals(snippet.get(1))); + assertTrue("file".equals(snippet.get(2))); + } + + public void testGetSnippetNoPadding() throws Exception { + URL url = ClassLoaderUtil.getResource("com/opensymphony/xwork2/somefile.txt", getClass()); + Location loc = new LocationImpl("foo", url.toString(), 3, 2); + + List snippet = loc.getSnippet(0); + assertNotNull(snippet); + assertTrue("Wrong length: "+snippet.size(), 1 == snippet.size()); + + assertTrue("a".equals(snippet.get(0))); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationUtilsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationUtilsTest.java new file mode 100644 index 000000000..6a1bcc98e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/location/LocationUtilsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005 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.util.location; + +import junit.framework.TestCase; + +public class LocationUtilsTest extends TestCase { + + public LocationUtilsTest(String name) { + super(name); + } + + static final String str = "path/to/file.xml:1:40"; + + public void testParse() throws Exception { + String str = " - path/to/file.xml:1:40"; + Location loc = LocationUtils.parse(str); + + assertEquals("", loc.getDescription()); + assertEquals("URI", "path/to/file.xml", loc.getURI()); + assertEquals("line", 1, loc.getLineNumber()); + assertEquals("column", 40, loc.getColumnNumber()); + assertEquals("string representation", str, loc.toString()); + } + + public void testGetLocation_location() throws Exception { + Location loc = new LocationImpl("desc", "sysId", 10, 4); + assertTrue("Location should be the same", + loc == LocationUtils.getLocation(loc, null)); + } + + public void testGetLocation_exception() throws Exception { + Exception e = new Exception(); + Location loc = LocationUtils.getLocation(e, null); + + assertTrue("Wrong sysId: "+loc.getURI(), + "com/opensymphony/xwork2/util/location/LocationUtilsTest.java" + .equals(loc.getURI())); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/logging/LoggerUtilsTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/logging/LoggerUtilsTest.java new file mode 100644 index 000000000..713964804 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/logging/LoggerUtilsTest.java @@ -0,0 +1,22 @@ +package com.opensymphony.xwork2.util.logging; + + +import junit.framework.TestCase; + +public class LoggerUtilsTest extends TestCase { + + public void testFormatMessage() { + assertEquals("foo", LoggerUtils.format("foo")); + assertEquals("foo #", LoggerUtils.format("foo #")); + assertEquals("#foo", LoggerUtils.format("#foo")); + assertEquals("foo #1", LoggerUtils.format("foo #1")); + assertEquals("foo bob", LoggerUtils.format("foo #0", "bob")); + assertEquals("foo bob joe", LoggerUtils.format("foo #0 #1", "bob", "joe")); + assertEquals("foo bob joe #8", LoggerUtils.format("foo #0 #1 #8", "bob", "joe")); + + assertEquals(null, LoggerUtils.format(null)); + assertEquals("", LoggerUtils.format("")); + + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBeanTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBeanTest.java new file mode 100644 index 000000000..91065658c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBeanTest.java @@ -0,0 +1,124 @@ +/* + * 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.util.profiling; + +import junit.framework.TestCase; + +/** + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class ProfilingTimerBeanTest extends TestCase { + + public void testAddChild() throws Exception { + ProfilingTimerBean bean0 = new ProfilingTimerBean("bean0"); + ProfilingTimerBean bean1 = new ProfilingTimerBean("bean1"); + ProfilingTimerBean bean2 = new ProfilingTimerBean("bean2"); + ProfilingTimerBean bean3 = new ProfilingTimerBean("bean3"); + ProfilingTimerBean bean4 = new ProfilingTimerBean("bean4"); + ProfilingTimerBean bean5 = new ProfilingTimerBean("bean5"); + ProfilingTimerBean bean6 = new ProfilingTimerBean("bean6"); + ProfilingTimerBean bean7 = new ProfilingTimerBean("bean7"); + ProfilingTimerBean bean8 = new ProfilingTimerBean("bean8"); + + /* bean0 + * + bean1 + * + bean2 + * + bean3 + * + bean4 + * + bean5 + * + bean6 + * +bean7 + * + bean8 + */ + + bean0.addChild(bean1); + bean0.addChild(bean3); + bean0.addChild(bean8); + + bean1.addChild(bean2); + + bean3.addChild(bean4); + bean3.addChild(bean7); + + bean4.addChild(bean5); + + bean5.addChild(bean6); + + + // bean0 + assertNull(bean0.getParent()); + assertEquals(bean0.children.size(), 3); + assertTrue(bean0.children.contains(bean1)); + assertTrue(bean0.children.contains(bean3)); + assertTrue(bean0.children.contains(bean8)); + + // bean1 + assertEquals(bean1.getParent(), bean0); + assertEquals(bean1.children.size(), 1); + assertTrue(bean1.children.contains(bean2)); + + // bean2 + assertEquals(bean2.getParent(), bean1); + assertEquals(bean2.children.size(), 0); + + // bean3 + assertEquals(bean3.getParent(), bean0); + assertEquals(bean3.children.size(), 2); + assertTrue(bean3.children.contains(bean4)); + assertTrue(bean3.children.contains(bean7)); + + // bean4 + assertEquals(bean4.getParent(), bean3); + assertEquals(bean4.children.size(), 1); + assertTrue(bean4.children.contains(bean5)); + + // bean5 + assertEquals(bean5.getParent(), bean4); + assertEquals(bean5.children.size(), 1); + assertTrue(bean5.children.contains(bean6)); + + // bean6 + assertEquals(bean6.getParent(), bean5); + assertEquals(bean6.children.size(), 0); + + // bean7 + assertEquals(bean7.getParent(), bean3); + assertEquals(bean7.children.size(), 0); + + // bean8 + assertEquals(bean8.getParent(), bean0); + assertEquals(bean8.children.size(), 0); + } + + public void testTime() throws Exception { + ProfilingTimerBean bean0 = new ProfilingTimerBean("bean0"); + bean0.setStartTime(); + Thread.sleep(1050); + bean0.setEndTime(); + assertTrue(bean0.totalTime >= 1000); + } + + public void testPrint() throws Exception { + ProfilingTimerBean bean0 = new ProfilingTimerBean("bean0"); + bean0.setStartTime(); + Thread.sleep(1050); + bean0.setEndTime(); + assertEquals(bean0.getPrintable(2000), ""); + assertTrue(bean0.getPrintable(500).length() > 0); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/profiling/UtilTimerStackTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/profiling/UtilTimerStackTest.java new file mode 100644 index 000000000..1520d7cbd --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/profiling/UtilTimerStackTest.java @@ -0,0 +1,133 @@ +/* + * 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.util.profiling; + +import junit.framework.TestCase; + +/** + * @author tmjee + * @version $Date$ $Id$ + */ +public class UtilTimerStackTest extends TestCase { + + protected String activateProp; + protected String minTimeProp; + + + public void testActivateInactivate() throws Exception { + UtilTimerStack.setActive(true); + assertTrue(UtilTimerStack.isActive()); + UtilTimerStack.setActive(false); + assertFalse(UtilTimerStack.isActive()); + } + + + public void testPushPop() throws Exception { + UtilTimerStack.push("p1"); + Thread.sleep(1050); + ProfilingTimerBean bean = UtilTimerStack.current.get(); + assertTrue(bean.startTime > 0); + UtilTimerStack.pop("p1"); + assertTrue(bean.totalTime > 1000); + } + + + public void testProfileCallback() throws Exception { + + MockProfilingBlock block = new MockProfilingBlock() { + @Override + public String performProfiling() throws Exception { + Thread.sleep(1050); + return "OK"; + } + }; + String result = UtilTimerStack.profile("p1", block); + assertEquals(result, "OK"); + assertNotNull(block.getProfilingTimerBean()); + assertTrue(block.getProfilingTimerBean().totalTime >= 1000); + + } + + + public void testProfileCallbackThrowsException() throws Exception { + try { + UtilTimerStack.profile("p1", + new UtilTimerStack.ProfilingBlock() { + public String doProfiling() throws Exception { + throw new RuntimeException("test"); + } + }); + fail("exception should have been thrown"); + } + catch (Exception e) { + assertTrue(true); + } + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + + activateProp = System.getProperty(UtilTimerStack.ACTIVATE_PROPERTY); + minTimeProp = System.getProperty(UtilTimerStack.MIN_TIME); + + System.setProperty(UtilTimerStack.ACTIVATE_PROPERTY, "true"); + UtilTimerStack.setActive(true); + System.setProperty(UtilTimerStack.MIN_TIME, "0"); + } + + + @Override + protected void tearDown() throws Exception { + + if (activateProp != null) { + System.setProperty(UtilTimerStack.ACTIVATE_PROPERTY, activateProp); + } else { + System.clearProperty(UtilTimerStack.ACTIVATE_PROPERTY); + } + if (minTimeProp != null) { + System.setProperty(UtilTimerStack.MIN_TIME, minTimeProp); + } else { + System.clearProperty(UtilTimerStack.ACTIVATE_PROPERTY); + } + + + activateProp = null; + minTimeProp = null; + + super.tearDown(); + } + + + public abstract class MockProfilingBlock implements UtilTimerStack.ProfilingBlock { + + private ProfilingTimerBean bean; + + public T doProfiling() throws Exception { + bean = UtilTimerStack.current.get(); + return performProfiling(); + } + + public ProfilingTimerBean getProfilingTimerBean() { + return bean; + } + + public abstract T performProfiling() throws Exception; + } +} + + diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ActionValidatorManagerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ActionValidatorManagerTest.java new file mode 100644 index 000000000..6d7eeeb69 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ActionValidatorManagerTest.java @@ -0,0 +1,216 @@ +package com.opensymphony.xwork2.validator; + +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator; +import com.opensymphony.xwork2.validator.validators.RequiredStringValidator; +import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A test case for ActionValidatorManager. + * + * @author tmjee + * @version $Date$ $Id$ + */ +public class ActionValidatorManagerTest extends XWorkTestCase { + + + + public void testValidate() throws Exception { + /* MockAction.class */ + // reference number + ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack(); + final RequiredStringValidator referenceNumberRequiredStringValidator = new RequiredStringValidator(); + referenceNumberRequiredStringValidator.setFieldName("referenceNumber"); + referenceNumberRequiredStringValidator.setDefaultMessage("Reference number is required"); + referenceNumberRequiredStringValidator.setValueStack(stack); + + // order + final RequiredFieldValidator orderRequiredValidator = new RequiredFieldValidator(); + orderRequiredValidator.setFieldName("order"); + orderRequiredValidator.setDefaultMessage("Order is required"); + orderRequiredValidator.setValueStack(stack); + + // customer + final RequiredFieldValidator customerRequiredValidator = new RequiredFieldValidator(); + customerRequiredValidator.setFieldName("customer"); + customerRequiredValidator.setDefaultMessage("Customer is required"); + customerRequiredValidator.setValueStack(stack); + final VisitorFieldValidator customerVisitorValidator = new VisitorFieldValidator(); + customerVisitorValidator.setAppendPrefix(true); + customerVisitorValidator.setFieldName("customer"); + customerVisitorValidator.setValueStack(stack); + + /* Customer.class */ + // customer -> name + final RequiredStringValidator customerNameRequiredStringValidator = new RequiredStringValidator(); + customerNameRequiredStringValidator.setFieldName("name"); + customerNameRequiredStringValidator.setDefaultMessage("Name is required"); + customerNameRequiredStringValidator.setValueStack(stack); + + // customer -> age + final RequiredFieldValidator customerAgeRequiredValidator = new RequiredFieldValidator(); + customerAgeRequiredValidator.setFieldName("age"); + customerAgeRequiredValidator.setDefaultMessage("Age is required"); + customerAgeRequiredValidator.setValueStack(stack); + + // customer -> Address + final RequiredFieldValidator customerAddressRequiredFieldValidator = new RequiredFieldValidator(); + customerAddressRequiredFieldValidator.setFieldName("address"); + customerAddressRequiredFieldValidator.setDefaultMessage("Address is required"); + customerAddressRequiredFieldValidator.setValueStack(stack); + + final VisitorFieldValidator customerAddressVisitorFieldValidator = new VisitorFieldValidator(); + customerAddressVisitorFieldValidator.setFieldName("address"); + customerAddressVisitorFieldValidator.setAppendPrefix(true); + //customerAddressVisitorFieldValidator.setDefaultMessage(""); + customerAddressVisitorFieldValidator.setValueStack(stack); + + + + /* Address.class */ + // customer -> Address -> street + final RequiredStringValidator customerAddressStreetRequiredFieldValidator = new RequiredStringValidator(); + customerAddressStreetRequiredFieldValidator.setFieldName("street"); + customerAddressStreetRequiredFieldValidator.setDefaultMessage("Street is required"); + customerAddressStreetRequiredFieldValidator.setShortCircuit(true); + customerAddressStreetRequiredFieldValidator.setValueStack(stack); + + final RequiredStringValidator customerAddressStreetRequiredFieldValidator2 = new RequiredStringValidator(); + customerAddressStreetRequiredFieldValidator2.setFieldName("street"); + customerAddressStreetRequiredFieldValidator2.setDefaultMessage("Street is required 2"); + customerAddressStreetRequiredFieldValidator2.setShortCircuit(true); + customerAddressStreetRequiredFieldValidator2.setValueStack(stack); + + // customer -> Address -> pobox + final RequiredStringValidator customerAddressPoboxRequiredFieldValidator = new RequiredStringValidator(); + customerAddressPoboxRequiredFieldValidator.setFieldName("pobox"); + customerAddressPoboxRequiredFieldValidator.setDefaultMessage("PO Box is required"); + customerAddressPoboxRequiredFieldValidator.setShortCircuit(false); + customerAddressPoboxRequiredFieldValidator.setValueStack(stack); + + final RequiredStringValidator customerAddressPoboxRequiredFieldValidator2 = new RequiredStringValidator(); + customerAddressPoboxRequiredFieldValidator2.setFieldName("pobox"); + customerAddressPoboxRequiredFieldValidator2.setDefaultMessage("PO Box is required 2"); + customerAddressPoboxRequiredFieldValidator2.setShortCircuit(false); + customerAddressPoboxRequiredFieldValidator2.setValueStack(stack); + + + + final List validatorsForMockAction = new ArrayList() { + { + add(referenceNumberRequiredStringValidator); + add(orderRequiredValidator); + add(customerRequiredValidator); + add(customerVisitorValidator); + } + }; + + final List validatorsForCustomer = new ArrayList() { + { + add(customerNameRequiredStringValidator); + add(customerAgeRequiredValidator); + add(customerAddressRequiredFieldValidator); + add(customerAddressVisitorFieldValidator); + } + }; + + final List validatorsForAddress = new ArrayList() { + { + add(customerAddressStreetRequiredFieldValidator); + add(customerAddressStreetRequiredFieldValidator2); + add(customerAddressPoboxRequiredFieldValidator); + add(customerAddressPoboxRequiredFieldValidator2); + } + }; + + + DefaultActionValidatorManager validatorManager = new DefaultActionValidatorManager() { + @Override + public List getValidators(Class clazz, String context, String method) { + if (clazz.isAssignableFrom(MockAction.class)) { + return validatorsForMockAction; + } + else if (clazz.isAssignableFrom(Customer.class)) { + return validatorsForCustomer; + } + else if (clazz.isAssignableFrom(Address.class)) { + return validatorsForAddress; + } + return Collections.emptyList(); + } + }; + customerVisitorValidator.setActionValidatorManager(validatorManager); + customerAddressVisitorFieldValidator.setActionValidatorManager(validatorManager); + + MockAction action = new MockAction(); + stack.push(action); + validatorManager.validate(action, "ctx"); + + assertFalse(action.hasActionErrors()); + assertFalse(action.hasActionMessages()); + assertTrue(action.hasFieldErrors()); + assertTrue(action.getFieldErrors().containsKey("referenceNumber")); + assertEquals((action.getFieldErrors().get("referenceNumber")).size(), 1); + assertTrue(action.getFieldErrors().containsKey("order")); + assertEquals((action.getFieldErrors().get("order")).size(), 1); + assertTrue(action.getFieldErrors().containsKey("customer.name")); + assertEquals((action.getFieldErrors().get("customer.name")).size(), 1); + assertTrue(action.getFieldErrors().containsKey("customer.age")); + assertEquals((action.getFieldErrors().get("customer.age")).size(), 1); + assertTrue(action.getFieldErrors().containsKey("customer.address.street")); + assertEquals((action.getFieldErrors().get("customer.address.street")).size(), 1); + assertTrue(action.getFieldErrors().containsKey("customer.address.pobox")); + assertEquals((action.getFieldErrors().get("customer.address.pobox")).size(), 2); + } + + private class MockAction extends ActionSupport { + + private String referenceNumber; + private Integer order; + private Customer customer = new Customer(); + + + public String getReferenceNumber() { return referenceNumber; } + public void setReferenceNumber(String referenceNumber) { this.referenceNumber = referenceNumber; } + + public Integer getOrder() { return order; } + public void setOrder(Integer order) { this.order = order; } + + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + } + + + private class Customer { + private String name; + private Integer age; + private Address address = new Address(); + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public Integer getAge() { return age; } + public void setAge(Integer age) { this.age = age; } + + public Address getAddress() { return address; } + public void setAddress(Address address) { this.address = address; } + } + + private class Address { + private String street; + private String pobox; + + public String getStreet() { return street; } + public void setStreet(String street) { this.street = street; } + + public String getPobox() { return pobox; } + public void setPobox(String pobox) { this.pobox = pobox; } + } +} \ No newline at end of file diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManagerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManagerTest.java new file mode 100644 index 000000000..a8a9916f5 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManagerTest.java @@ -0,0 +1,395 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.test.AnnotationDataAware2; +import com.opensymphony.xwork2.test.AnnotationUser; +import com.opensymphony.xwork2.test.SimpleAnnotationAction2; +import com.opensymphony.xwork2.test.SimpleAnnotationAction3; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.validator.validators.*; + +import java.util.List; + +import org.easymock.EasyMock; + + +/** + * AnnotationActionValidatorManagerTest + * + * @author Rainer Hermanns + * @author Jason Carreira + * @author tm_jee ( tm_jee (at) yahoo.co.uk ) + * Created Jun 9, 2003 11:03:01 AM + */ +public class AnnotationActionValidatorManagerTest extends XWorkTestCase { + + protected final String alias = "annotationValidationAlias"; + + AnnotationActionValidatorManager annotationActionValidatorManager; + + @Override protected void setUp() throws Exception { + super.setUp(); + annotationActionValidatorManager = (AnnotationActionValidatorManager) container.getInstance(ActionValidatorManager.class); + + ActionConfig config = new ActionConfig.Builder("", "name", "").build(); + ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class); + ActionProxy proxy = EasyMock.createNiceMock(ActionProxy.class); + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(null).anyTimes(); + EasyMock.expect(invocation.invoke()).andReturn(Action.SUCCESS).anyTimes(); + EasyMock.expect(proxy.getMethod()).andReturn("execute").anyTimes(); + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(proxy); + + ActionContext.getContext().setActionInvocation(invocation); + } + + @Override protected void tearDown() throws Exception { + annotationActionValidatorManager = null; + super.tearDown(); + } + + public void testBuildValidatorKey() { + String validatorKey = AnnotationActionValidatorManager.buildValidatorKey(SimpleAnnotationAction.class); + assertEquals(SimpleAnnotationAction.class.getName() + "/name|execute", validatorKey); + } + + public void testBuildsValidatorsForAlias() { + List validatorList = annotationActionValidatorManager.getValidators(SimpleAnnotationAction.class, alias); + + // 17 in the class level + 0 in the alias + // TODO: add alias tests + assertEquals(17, validatorList.size()); + } + + public void testGetValidatorsForGivenMethodNameWithoutReloading() throws ValidationException { + List validatorList = annotationActionValidatorManager.getValidators(SimpleAnnotationAction.class, alias, "execute"); + + //disable configuration reload/devmode + FileManager.setReloadingConfigs(false); + + //17 in the class level + 0 in the alias + assertEquals(12, validatorList.size()); + + validatorList = annotationActionValidatorManager.getValidators(SimpleAnnotationAction.class, alias, "execute"); + + //expect same number of validators + assertEquals(12, validatorList.size()); + } + + public void testDefaultMessageInterpolation() { + // get validators + List validatorList = annotationActionValidatorManager.getValidators(AnnotatedTestBean.class, "beanMessageBundle"); + assertEquals(3, validatorList.size()); + + try { + AnnotatedTestBean bean = new AnnotatedTestBean(); + bean.setName("foo"); + bean.setCount(99); + + ValidatorContext context = new GenericValidatorContext(bean); + annotationActionValidatorManager.validate(bean, "beanMessageBundle", context); + assertTrue(context.hasErrors()); + assertTrue(context.hasFieldErrors()); + + List l = context.getFieldErrors().get("count"); + assertNotNull(l); + assertEquals(1, l.size()); + assertEquals("Smaller Invalid Count: 99", l.get(0)); + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + public void testGetValidatorsForInterface() { + List validatorList = annotationActionValidatorManager.getValidators(AnnotationDataAware2.class, alias); + + // 1 in interface hierarchy, 2 from parent interface (1 default + 1 context) + assertEquals(3, validatorList.size()); + + final FieldValidator dataValidator1 = (FieldValidator) validatorList.get(0); + assertEquals("data", dataValidator1.getFieldName()); + assertTrue(dataValidator1 instanceof RequiredFieldValidator); + + final FieldValidator dataValidator2 = (FieldValidator) validatorList.get(1); + assertEquals("data", dataValidator2.getFieldName()); + assertTrue(dataValidator2 instanceof RequiredStringValidator); + + final FieldValidator blingValidator = (FieldValidator) validatorList.get(2); + assertEquals("bling", blingValidator.getFieldName()); + assertTrue(blingValidator instanceof RequiredStringValidator); + } + + public void no_testGetValidatorsFromInterface() { + List validatorList = annotationActionValidatorManager.getValidators(SimpleAnnotationAction3.class, alias); + + // 17 in the class hierarchy + 1 in the interface + 1 in interface alias + assertEquals(19, validatorList.size()); + + final FieldValidator v = (FieldValidator) validatorList.get(0); + assertEquals("bar", v.getFieldName()); + assertTrue(v instanceof RequiredFieldValidator); + + final FieldValidator v1 = (FieldValidator) validatorList.get(1); + assertEquals("bar", v1.getFieldName()); + assertTrue(v1 instanceof IntRangeFieldValidator); + + final FieldValidator vdouble = (FieldValidator) validatorList.get(2); + assertEquals("percentage", vdouble.getFieldName()); + assertTrue(vdouble instanceof DoubleRangeFieldValidator); + + final FieldValidator v2 = (FieldValidator) validatorList.get(3); + assertEquals("baz", v2.getFieldName()); + assertTrue(v2 instanceof IntRangeFieldValidator); + + final FieldValidator v3 = (FieldValidator) validatorList.get(4); + assertEquals("date", v3.getFieldName()); + assertTrue(v3 instanceof DateRangeFieldValidator); + + // action-level validator comes first + final Validator v4 = (Validator) validatorList.get(5); + assertTrue(v4 instanceof ExpressionValidator); + + // action-level validator comes first + final Validator v5 = (Validator) validatorList.get(6); + assertTrue(v5 instanceof ExpressionValidator); + + // action-level validator comes first + final Validator v6 = (Validator) validatorList.get(7); + assertTrue(v6 instanceof ExpressionValidator); + + // action-level validator comes first + final Validator v7 = (Validator) validatorList.get(8); + assertTrue(v7 instanceof ExpressionValidator); + + // action-level validator comes first + final Validator v8 = (Validator) validatorList.get(9); + assertTrue(v8 instanceof ExpressionValidator); + + final FieldValidator v9 = (FieldValidator) validatorList.get(10); + assertEquals("datefield", v9.getFieldName()); + assertTrue(v9 instanceof DateRangeFieldValidator); + + final FieldValidator v10 = (FieldValidator) validatorList.get(11); + assertEquals("emailaddress", v10.getFieldName()); + assertTrue(v10 instanceof EmailValidator); + + final FieldValidator v11 = (FieldValidator) validatorList.get(12); + assertEquals("intfield", v11.getFieldName()); + assertTrue(v11 instanceof IntRangeFieldValidator); + + final FieldValidator v12 = (FieldValidator) validatorList.get(13); + assertEquals("customfield", v12.getFieldName()); + assertTrue(v12 instanceof RequiredFieldValidator); + + final FieldValidator v13 = (FieldValidator) validatorList.get(14); + assertEquals("stringisrequired", v13.getFieldName()); + assertTrue(v13 instanceof RequiredStringValidator); + + final FieldValidator v14 = (FieldValidator) validatorList.get(15); + assertEquals("needstringlength", v14.getFieldName()); + assertTrue(v14 instanceof StringLengthFieldValidator); + + final FieldValidator v15 = (FieldValidator) validatorList.get(16); + assertEquals("hreflocation", v15.getFieldName()); + assertTrue(v15 instanceof URLValidator); + + final FieldValidator v16 = (FieldValidator) validatorList.get(17); + assertEquals("data", v16.getFieldName()); + assertTrue(v16 instanceof RequiredFieldValidator); + + final FieldValidator v17 = (FieldValidator) validatorList.get(18); + assertEquals("data", v17.getFieldName()); + assertTrue(v17 instanceof RequiredStringValidator); + + } + + public void testMessageInterpolation() { + // get validators + List validatorList = annotationActionValidatorManager.getValidators(AnnotatedTestBean.class, "beanMessageBundle"); + assertEquals(3, validatorList.size()); + + try { + AnnotatedTestBean bean = new AnnotatedTestBean(); + bean.setName("foo"); + bean.setCount(150); + + ValidatorContext context = new GenericValidatorContext(bean); + annotationActionValidatorManager.validate(bean, "beanMessageBundle", context); + assertTrue(context.hasErrors()); + assertTrue(context.hasFieldErrors()); + + List l = context.getFieldErrors().get("count"); + assertNotNull(l); + assertEquals(1, l.size()); + assertEquals("Count must be between 1 and 100, current value is 150.", l.get(0)); + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + public void testSameAliasWithDifferentClass() { + List validatorList = annotationActionValidatorManager.getValidators(SimpleAnnotationAction.class, alias); + List validatorList2 = annotationActionValidatorManager.getValidators(SimpleAnnotationAction2.class, alias); + assertFalse(validatorList.size() == validatorList2.size()); + } + + public void testSameAliasWithAliasWithSlashes() { + List validatorList = annotationActionValidatorManager.getValidators(SimpleAction.class, "some/alias"); + assertNotNull(validatorList); + assertEquals(11, validatorList.size()); + } + + public void testSkipUserMarkerActionLevelShortCircuit() { + // get validators + List validatorList = annotationActionValidatorManager.getValidators(AnnotationUser.class, null); + assertEquals(10, validatorList.size()); + + try { + AnnotationUser user = new AnnotationUser(); + user.setName("Mark"); + user.setEmail("bad_email"); + user.setEmail2("bad_email"); + + ValidatorContext context = new GenericValidatorContext(user); + annotationActionValidatorManager.validate(user, null, context); + assertTrue(context.hasFieldErrors()); + + // check field errors + List l = context.getFieldErrors().get("email"); + assertNotNull(l); + assertEquals(1, l.size()); + assertEquals("Not a valid e-mail.", l.get(0)); + l = context.getFieldErrors().get("email2"); + assertNotNull(l); + assertEquals(2, l.size()); + assertEquals("Not a valid e-mail2.", l.get(0)); + assertEquals("Email2 not from the right company.", l.get(1)); + + // check action errors + assertTrue(context.hasActionErrors()); + l = (List) context.getActionErrors(); + assertNotNull(l); + assertEquals(2, l.size()); // both expression test failed see AnnotationUser-validation.xml + assertEquals("Email does not start with mark", l.get(0)); + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + public void testSkipAllActionLevelShortCircuit2() { + // get validators + List validatorList = annotationActionValidatorManager.getValidators(AnnotationUser.class, null); + assertEquals(10, validatorList.size()); + + try { + AnnotationUser user = new AnnotationUser(); + user.setName("Mark"); + // * mark both email to starts with mark to get pass the action-level validator, + // so we could concentrate on testing the field-level validators (AnnotationUser-validation.xml) + // * make both email the same to pass the action-level validator at + // AnnotationUserMarker-validation.xml + user.setEmail("mark_bad_email_for_field_val@foo.com"); + user.setEmail2("mark_bad_email_for_field_val@foo.com"); + + ValidatorContext context = new GenericValidatorContext(user); + annotationActionValidatorManager.validate(user, null, context); + assertTrue(context.hasFieldErrors()); + + // check field errors + // we have an error in this field level, email does not ends with mycompany.com + List l = (List) context.getFieldErrors().get("email"); + assertNotNull(l); + assertEquals(1, l.size()); // because email-field-val is short-circuit + assertEquals("Email not from the right company.", l.get(0)); + + + // check action errors + l = (List) context.getActionErrors(); + assertFalse(context.hasActionErrors()); + assertEquals(0, l.size()); + + + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + + public void testActionLevelShortCircuit() throws Exception { + + List validatorList = annotationActionValidatorManager.getValidators(AnnotationUser.class, null); + assertEquals(10, validatorList.size()); + + AnnotationUser user = new AnnotationUser(); + // all fields will trigger error, but sc of action-level, cause it to not appear + user.setName(null); + + user.setEmail("rainerh(at)example.com"); + user.setEmail("rainer_h(at)example.com"); + + + ValidatorContext context = new GenericValidatorContext(user); + annotationActionValidatorManager.validate(user, null, context); + + // check field level errors + // shouldn't have any because action error prevents validation of anything else + List l = (List) context.getFieldErrors().get("email2"); + assertNull(l); + + + // check action errors + assertTrue(context.hasActionErrors()); + l = (List) context.getActionErrors(); + assertNotNull(l); + // we only get one, because AnnotationUserMarker-validation.xml action-level validator + // already sc it :-) + assertEquals(1, l.size()); + assertEquals("Email not the same as email2", l.get(0)); + } + + + public void testShortCircuitNoErrors() { + // get validators + List validatorList = annotationActionValidatorManager.getValidators(AnnotationUser.class, null); + assertEquals(10, validatorList.size()); + + try { + AnnotationUser user = new AnnotationUser(); + user.setName("Mark"); + user.setEmail("mark@mycompany.com"); + user.setEmail2("mark@mycompany.com"); + + ValidatorContext context = new GenericValidatorContext(user); + annotationActionValidatorManager.validate(user, null, context); + assertFalse(context.hasErrors()); + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ConversionErrorFieldValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ConversionErrorFieldValidatorTest.java new file mode 100644 index 000000000..9faec48d3 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ConversionErrorFieldValidatorTest.java @@ -0,0 +1,84 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.ValidationAwareSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * ConversionErrorFieldValidatorTest + * + * @author Jason Carreira + * Date: Nov 28, 2003 3:45:37 PM + */ +public class ConversionErrorFieldValidatorTest extends XWorkTestCase { + + private static final String defaultFooMessage = "Invalid field value for field \"foo\"."; + + + private ConversionErrorFieldValidator validator; + private ValidationAware validationAware; + + + @Override + public void setUp() throws Exception { + super.setUp(); + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext context = new ActionContext(stack.getContext()); + + Map conversionErrors = new HashMap(); + conversionErrors.put("foo", "bar"); + context.setConversionErrors(conversionErrors); + validator = new ConversionErrorFieldValidator(); + validationAware = new ValidationAwareSupport(); + + DelegatingValidatorContext validatorContext = new DelegatingValidatorContext(validationAware); + stack.push(validatorContext); + validator.setValidatorContext(validatorContext); + validator.setFieldName("foo"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + assertEquals(0, validationAware.getFieldErrors().size()); + } + + public void testConversionErrorMessageUsesProvidedMessage() throws ValidationException { + String message = "default message"; + validator.setDefaultMessage(message); + validator.validate(validationAware); + + + Map fieldErrors = validationAware.getFieldErrors(); + assertTrue(fieldErrors.containsKey("foo")); + assertEquals(message, ((List) fieldErrors.get("foo")).get(0)); + } + + public void testConversionErrorsAreAddedToFieldErrors() throws ValidationException { + validator.validate(validationAware); + + Map fieldErrors = validationAware.getFieldErrors(); + assertTrue(fieldErrors.containsKey("foo")); + assertEquals(defaultFooMessage, ((List) fieldErrors.get("foo")).get(0)); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DateRangeValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DateRangeValidatorTest.java new file mode 100644 index 000000000..b57a482af --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DateRangeValidatorTest.java @@ -0,0 +1,82 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator; + +import java.util.*; + + +/** + * DateRangeValidatorTest + * + * @author Jason Carreira + * Created Feb 9, 2003 1:25:42 AM + */ +public class DateRangeValidatorTest extends XWorkTestCase { + + private Locale origLocale; + + + /** + * Tests whether the date range validation is working. Should produce an validation error, + * because the action config sets date to 12/20/2002 while expected range is Dec 22-25. + */ + public void testRangeValidation() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, null); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + Iterator it = errors.entrySet().iterator(); + + List errorMessages = (List) errors.get("date"); + assertNotNull("Expected date range validation error message.", errorMessages); + assertEquals(1, errorMessages.size()); + + String errorMessage = (String) errorMessages.get(0); + assertNotNull(errorMessage); + } + + public void testGetSetMinMax() throws Exception { + DateRangeFieldValidator val = new DateRangeFieldValidator(); + Date max = new Date(); + val.setMax(max); + assertEquals(max, val.getMax()); + + Date min = new Date(); + val.setMin(min); + assertEquals(min, val.getMin()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + origLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + loadConfigurationProviders(new MockConfigurationProvider()); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + Locale.setDefault(origLocale); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManagerTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManagerTest.java new file mode 100644 index 000000000..d661e28b1 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManagerTest.java @@ -0,0 +1,349 @@ +/* + * 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.validator; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.test.DataAware2; +import com.opensymphony.xwork2.test.SimpleAction2; +import com.opensymphony.xwork2.test.SimpleAction3; +import com.opensymphony.xwork2.util.ValueStack; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + + +/** + * DefaultActionValidatorManagerTest + * + * @author Jason Carreira + * @author tm_jee + * @version $Date$ $Id$ + */ +public class DefaultActionValidatorManagerTest extends TestCase { + + protected final String alias = "validationAlias"; + + DefaultActionValidatorManager actionValidatorManager; + Mock mockValidatorFileParser; + Mock mockValidatorFactory; + ValueStack stubValueStack; + + @Override + protected void setUp() throws Exception { + actionValidatorManager = new DefaultActionValidatorManager(); + super.setUp(); + mockValidatorFileParser = new Mock(ValidatorFileParser.class); + actionValidatorManager.setValidatorFileParser((ValidatorFileParser)mockValidatorFileParser.proxy()); + + mockValidatorFactory = new Mock(ValidatorFactory.class); + actionValidatorManager.setValidatorFactory((ValidatorFactory)mockValidatorFactory.proxy()); + + stubValueStack = new StubValueStack(); + ActionContext.setContext(new ActionContext(new HashMap())); + ActionContext.getContext().setValueStack(stubValueStack); + + } + + @Override + protected void tearDown() throws Exception { + actionValidatorManager = null; + super.tearDown(); + mockValidatorFactory = null; + mockValidatorFileParser = null; + } + + + public void testBuildValidatorKey() { + String validatorKey = DefaultActionValidatorManager.buildValidatorKey(SimpleAction.class, alias); + assertEquals(SimpleAction.class.getName() + "/" + alias, validatorKey); + } + + public void testBuildsValidatorsForAlias() { + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")), + new ArrayList()); + actionValidatorManager.getValidators(SimpleAction.class, alias); + mockValidatorFileParser.verify(); + } + + public void testBuildsValidatorsForAliasError() { + boolean pass = false; + try { + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/TestBean-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndThrow("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/TestBean-badtest-validation.xml")), + new ConfigurationException()); + List validatorList = actionValidatorManager.getValidators(TestBean.class, "badtest"); + } catch (XWorkException ex) { + pass = true; + } + mockValidatorFileParser.verify(); + assertTrue("Didn't throw exception on load failure", pass); + } + + + public void testGetValidatorsForInterface() { + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware2-validation.xml")), + new ArrayList()); + actionValidatorManager.getValidators(DataAware2.class, alias); + mockValidatorFileParser.verify(); + } + + public void testGetValidatorsFromInterface() { + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml")), + new ArrayList()); + actionValidatorManager.getValidators(SimpleAction3.class, alias); + mockValidatorFileParser.verify(); + } + + public void testSameAliasWithDifferentClass() { + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/SimpleAction2-validation.xml")), + new ArrayList()); + mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs", + C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/SimpleAction2-validationAlias-validation.xml")), + new ArrayList()); + actionValidatorManager.getValidators(SimpleAction.class, alias); + actionValidatorManager.getValidators(SimpleAction2.class, alias); + mockValidatorFileParser.verify(); + } + + /* + // TODO: this all need to be converted to real unit tests + + public void testSkipUserMarkerActionLevelShortCircuit() { + // get validators + List validatorList = actionValidatorManager.getValidators(User.class, null); + assertEquals(10, validatorList.size()); + + try { + User user = new User(); + user.setName("Mark"); + user.setEmail("bad_email"); + user.setEmail2("bad_email"); + + ValidatorContext context = new GenericValidatorContext(user); + actionValidatorManager.validate(user, null, context); + assertTrue(context.hasFieldErrors()); + + // check field errors + List l = (List) context.getFieldErrors().get("email"); + assertNotNull(l); + assertEquals(1, l.size()); + assertEquals("Not a valid e-mail.", l.get(0)); + l = (List) context.getFieldErrors().get("email2"); + assertNotNull(l); + assertEquals(2, l.size()); + assertEquals("Not a valid e-mail2.", l.get(0)); + assertEquals("Email2 not from the right company.", l.get(1)); + + // check action errors + assertTrue(context.hasActionErrors()); + l = (List) context.getActionErrors(); + assertNotNull(l); + assertEquals(2, l.size()); // both expression test failed see User-validation.xml + assertEquals("Email does not start with mark", l.get(0)); + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + public void testSkipAllActionLevelShortCircuit2() { + // get validators + List validatorList = actionValidatorManager.getValidators(User.class, null); + assertEquals(10, validatorList.size()); + + try { + User user = new User(); + user.setName("Mark"); + // * mark both email to starts with mark to get pass the action-level validator, + // so we could concentrate on testing the field-level validators (User-validation.xml) + // * make both email the same to pass the action-level validator at + // UserMarker-validation.xml + user.setEmail("mark_bad_email_for_field_val@foo.com"); + user.setEmail2("mark_bad_email_for_field_val@foo.com"); + + ValidatorContext context = new GenericValidatorContext(user); + actionValidatorManager.validate(user, null, context); + assertTrue(context.hasFieldErrors()); + + // check field errors + // we have an error in this field level, email does not ends with mycompany.com + List l = (List) context.getFieldErrors().get("email"); + assertNotNull(l); + assertEquals(1, l.size()); // because email-field-val is short-circuit + assertEquals("Email not from the right company.", l.get(0)); + + + // check action errors + l = (List) context.getActionErrors(); + assertFalse(context.hasActionErrors()); + assertEquals(0, l.size()); + + + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + + public void testActionLevelShortCircuit() throws Exception { + + List validatorList = actionValidatorManager.getValidators(User.class, null); + assertEquals(10, validatorList.size()); + + User user = new User(); + // all fields will trigger error, but sc of action-level, cause it to not appear + user.setName(null); + user.setEmail("tmjee(at)yahoo.co.uk"); + user.setEmail("tm_jee(at)yahoo.co.uk"); + + ValidatorContext context = new GenericValidatorContext(user); + actionValidatorManager.validate(user, null, context); + + // check field level errors + // shouldn't have any because action error prevents validation of anything else + List l = (List) context.getFieldErrors().get("email2"); + assertNull(l); + + + // check action errors + assertTrue(context.hasActionErrors()); + l = (List) context.getActionErrors(); + assertNotNull(l); + // we only get one, because UserMarker-validation.xml action-level validator + // already sc it :-) + assertEquals(1, l.size()); + assertEquals("Email not the same as email2", l.get(0)); + } + + + public void testShortCircuitNoErrors() { + // get validators + List validatorList = actionValidatorManager.getValidators(User.class, null); + assertEquals(10, validatorList.size()); + + try { + User user = new User(); + user.setName("Mark"); + user.setEmail("mark@mycompany.com"); + user.setEmail2("mark@mycompany.com"); + + ValidatorContext context = new GenericValidatorContext(user); + actionValidatorManager.validate(user, null, context); + assertFalse(context.hasErrors()); + } catch (ValidationException ex) { + ex.printStackTrace(); + fail("Validation error: " + ex.getMessage()); + } + } + + public void testFieldErrorsOrder() throws Exception { + ValidationOrderAction action = new ValidationOrderAction(); + actionValidatorManager.validate(action, "actionContext"); + Map fieldErrors = action.getFieldErrors(); + Iterator i = fieldErrors.entrySet().iterator(); + + assertNotNull(fieldErrors); + assertEquals(fieldErrors.size(), 12); + + + Map.Entry e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "username"); + assertEquals(((List)e.getValue()).get(0), "username required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "password"); + assertEquals(((List)e.getValue()).get(0), "password required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "confirmPassword"); + assertEquals(((List)e.getValue()).get(0), "confirm password required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "firstName"); + assertEquals(((List)e.getValue()).get(0), "first name required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "lastName"); + assertEquals(((List)e.getValue()).get(0), "last name required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "city"); + assertEquals(((List)e.getValue()).get(0), "city is required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "province"); + assertEquals(((List)e.getValue()).get(0), "province is required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "country"); + assertEquals(((List)e.getValue()).get(0), "country is required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "postalCode"); + assertEquals(((List)e.getValue()).get(0), "postal code is required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "email"); + assertEquals(((List)e.getValue()).get(0), "email is required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "website"); + assertEquals(((List)e.getValue()).get(0), "website is required"); + + e = (Map.Entry) i.next(); + assertEquals(e.getKey(), "passwordHint"); + assertEquals(((List)e.getValue()).get(0), "password hint is required"); + + } + */ +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultValidatorFactoryTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultValidatorFactoryTest.java new file mode 100644 index 000000000..7cb10dd31 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultValidatorFactoryTest.java @@ -0,0 +1,38 @@ +/* + * 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.validator; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import junit.framework.TestCase; + +/** + * DefaultValidatorFactoryTest + * + * @author Rainer Hermanns + * @version $Id$ + */ +public class DefaultValidatorFactoryTest extends TestCase { + + public void testParseValidators() { + Mock mockValidatorFileParser = new Mock(ValidatorFileParser.class); + mockValidatorFileParser.expect("parseValidatorDefinitions", C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/validator/validators/default.xml"))); + mockValidatorFileParser.expect("parseValidatorDefinitions", C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("validators.xml"))); + mockValidatorFileParser.expect("parseValidatorDefinitions", C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("myOther-validators.xml"))); + mockValidatorFileParser.expect("parseValidatorDefinitions", C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("my-validators.xml"))); + DefaultValidatorFactory factory = new DefaultValidatorFactory(null, (ValidatorFileParser) mockValidatorFileParser.proxy()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParserTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParserTest.java new file mode 100644 index 000000000..58b2c350c --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParserTest.java @@ -0,0 +1,226 @@ +/* + * 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.validator; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.validator.validators.*; +import junit.framework.TestCase; + +import java.io.InputStream; +import java.util.List; + + +/** + * DefaultValidatorFileParserTest + *

+ * Created : Jan 20, 2003 3:41:26 PM + * + * @author Jason Carreira + * @author James House + * @author tm_jee ( tm_jee (at) yahoo.co.uk ) + * @author Martin Gilday + */ +public class DefaultValidatorFileParserTest extends TestCase { + + private static final String testFileName = "com/opensymphony/xwork2/validator/validator-parser-test.xml"; + private static final String testFileName2 = "com/opensymphony/xwork2/validator/validator-parser-test2.xml"; + private static final String testFileName3 = "com/opensymphony/xwork2/validator/validator-parser-test3.xml"; + private static final String testFileName4 = "com/opensymphony/xwork2/validator/validator-parser-test4.xml"; + private static final String testFileName5 = "com/opensymphony/xwork2/validator/validator-parser-test5.xml"; + private static final String testFileName6 = "com/opensymphony/xwork2/validator/validator-parser-test6.xml"; + private static final String testFileNameFail = "com/opensymphony/xwork2/validator/validators-fail.xml"; + private Mock mockValidatorFactory; + private ValidatorFileParser parser; + + public void testParserActionLevelValidatorsShouldBeBeforeFieldLevelValidators() throws Exception { + InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName2, this.getClass()); + + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("expression")), ExpressionValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("required")), RequiredFieldValidator.class.getName()); + List configs = parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileName2); + mockValidatorFactory.verify(); + + ValidatorConfig valCfg0 = (ValidatorConfig) configs.get(0); + ValidatorConfig valCfg1 = (ValidatorConfig) configs.get(1); + + assertNotNull(configs); + assertEquals(configs.size(), 2); + + assertEquals("expression", valCfg0.getType()); + assertFalse(valCfg0.isShortCircuit()); + assertEquals(valCfg0.getDefaultMessage(), "an expression error message"); + assertEquals(valCfg0.getParams().get("expression"), "false"); + + assertEquals("required", valCfg1.getType()); + assertFalse(valCfg1.isShortCircuit()); + assertEquals(valCfg1.getDefaultMessage(), "a field error message"); + } + + + public void testParser() { + InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName, this.getClass()); + + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("expression")), ExpressionValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("expression")), ExpressionValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("required")), RequiredFieldValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("required")), RequiredFieldValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("int")), IntRangeFieldValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("regex")), RegexFieldValidator.class.getName()); + List configs = parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileName); + mockValidatorFactory.verify(); + + + assertNotNull(configs); + assertEquals(6, configs.size()); + + + ValidatorConfig cfg = (ValidatorConfig) configs.get(0); + assertEquals("expression", cfg.getType()); + assertFalse(cfg.isShortCircuit()); + + cfg = (ValidatorConfig) configs.get(1); + assertEquals("expression", cfg.getType()); + assertTrue(cfg.isShortCircuit()); + + cfg = (ValidatorConfig) configs.get(2); + assertEquals("required", cfg.getType()); + assertEquals("foo", cfg.getParams().get("fieldName")); + assertEquals("You must enter a value for foo.", cfg.getDefaultMessage()); + assertEquals(4, cfg.getLocation().getLineNumber()); + + cfg = (ValidatorConfig) configs.get(3); + assertEquals("required", cfg.getType()); + assertTrue(cfg.isShortCircuit()); + + cfg = (ValidatorConfig) configs.get(4); + assertEquals("int", cfg.getType()); + assertFalse(cfg.isShortCircuit()); + + cfg = (ValidatorConfig) configs.get(5); + assertEquals("regex", cfg.getType()); + assertFalse(cfg.isShortCircuit()); + assertEquals("([aAbBcCdD][123][eEfFgG][456])", cfg.getParams().get("expression")); + } + + public void testParserWithBadValidation() { + InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName3, this.getClass()); + + boolean pass = false; + try { + parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileName3); + } catch (XWorkException ex) { + assertTrue("Wrong line number", 3 == ex.getLocation().getLineNumber()); + pass = true; + } + assertTrue("Validation file should have thrown exception", pass); + } + + public void testParserWithBadXML() { + InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName4, this.getClass()); + + boolean pass = false; + try { + parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileName4); + } catch (XWorkException ex) { + assertTrue("Wrong line number: " + ex.getLocation(), 13 == ex.getLocation().getLineNumber()); + pass = true; + } + assertTrue("Validation file should have thrown exception", pass); + } + + public void testParserWithBadXML2() { + InputStream is = ClassLoaderUtil.getResourceAsStream(testFileNameFail, this.getClass()); + + boolean pass = false; + try { + parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileNameFail); + } catch (XWorkException ex) { + assertTrue("Wrong line number: " + ex.getLocation(), 8 == ex.getLocation().getLineNumber()); + pass = true; + } + assertTrue("Validation file should have thrown exception", pass); + } + + public void testValidatorDefinitionsWithBadClassName() { + InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName5, this.getClass()); + + boolean pass = false; + try { + parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileName5); + } catch (XWorkException ex) { + assertTrue("Wrong line number", 3 == ex.getLocation().getLineNumber()); + pass = true; + } + assertTrue("Validation file should have thrown exception", pass); + } + + public void testValidatorWithI18nMessage() throws Exception { + InputStream is = null; + try { + is = ClassLoaderUtil.getResourceAsStream(testFileName6, this.getClass()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("requiredstring")), RequiredStringValidator.class.getName()); + mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("requiredstring")), RequiredStringValidator.class.getName()); + + List validatorConfigs = parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, "-//OpenSymphony Group//XWork Validator 1.0.3//EN"); + mockValidatorFactory.verify(); + + assertEquals(validatorConfigs.size(), 2); + + assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getParams().get("fieldName"), "name"); + assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getMessageParams().length, 0); + assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getMessageKey(), "error.name"); + assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getDefaultMessage(), "default message 1"); + assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getParams().size(), 1); + assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getType(), "requiredstring"); + + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().get("fieldName"), "address"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams().length, 5); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[0], "'tmjee'"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[1], "'phil'"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[2], "'rainer'"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[3], "'hopkins'"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[4], "'jimmy'"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageKey(), "error.address"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getDefaultMessage(), "The Default Message"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().size(), 3); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().get("trim"), "true"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().get("anotherParam"), "anotherValue"); + assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getType(), "requiredstring"); + } + finally { + if (is != null) { + is.close(); + } + } + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + mockValidatorFactory = new Mock(ValidatorFactory.class); + parser = new DefaultValidatorFileParser(); + } + + @Override + protected void tearDown() throws Exception { + mockValidatorFactory = null; + parser = null; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DoubleRangeValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DoubleRangeValidatorTest.java new file mode 100644 index 000000000..89277251b --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/DoubleRangeValidatorTest.java @@ -0,0 +1,221 @@ +package com.opensymphony.xwork2.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator; + +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Unit test for {@link DoubleRangeFieldValidator}. + * + * @author Rainer Hermanns + * @author Claus Ibsen + * @version $Id$ + */ +public class DoubleRangeValidatorTest extends XWorkTestCase { + private DoubleRangeFieldValidator val; + + public void testRangeValidationWithError() throws Exception { + // must set a locale to US as error message contains a locale dependent number (see XW-490) + Locale defLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, null); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + Iterator it = errors.entrySet().iterator(); + + List errorMessages = errors.get("percentage"); + assertNotNull("Expected double range validation error message.", errorMessages); + assertEquals(1, errorMessages.size()); + + String errorMessage = errorMessages.get(0); + assertNotNull("Expecting: percentage must be between 0.1 and 10.1, current value is 100.0123.", errorMessage); + assertEquals("percentage must be between 0.1 and 10.1, current value is 100.0123.", errorMessage); + + Locale.setDefault(defLocale); + } + + public void testRangeValidationNoError() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy("", "percentage", null); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + Iterator it = errors.entrySet().iterator(); + + List errorMessages = errors.get("percentage"); + assertNull("Expected no double range validation error message.", errorMessages); + } + + public void testRangeNoExclusiveAndNoValueInStack() throws Exception { + val.setFieldName("hello"); + val.validate("world"); + } + + public void testRangeSimpleDoubleValueInStack() throws Exception { + MyTestProduct prod = new MyTestProduct(); + prod.setName("coca cola"); + prod.setPrice(5.99); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(prod); + ActionContext.getContext().setValueStack(stack); + + val.setMinInclusive("0"); + val.setMaxInclusive("10"); + val.setFieldName("price"); + val.validate(prod); + } + + public void testRangeRealDoubleValueInStack() throws Exception { + MyTestProduct prod = new MyTestProduct(); + prod.setName("coca cola"); + prod.setPrice(5.99); + prod.setVolume(new Double(12.34)); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(prod); + ActionContext.getContext().setValueStack(stack); + + val.setMinInclusive("0"); + val.setMaxInclusive("30"); + val.setFieldName("volume"); + val.validate(prod); + } + + public void testRangeNotADoubleObjectValueInStack() throws Exception { + MyTestProduct prod = new MyTestProduct(); + prod.setName("coca cola"); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(prod); + ActionContext.getContext().setValueStack(stack); + + val.setMinInclusive("0"); + val.setMaxInclusive("10"); + val.setFieldName("name"); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + val.setValidatorContext(context); + + val.validate(prod); + + assertEquals("0", val.getMinInclusive()); + assertEquals("10", val.getMaxInclusive()); + } + + public void testEdgeOfMaxRange() throws Exception { + MyTestProduct prod = new MyTestProduct(); + prod.setName("coca cola"); + prod.setPrice(9.95); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(prod); + ActionContext.getContext().setValueStack(stack); + + val.setFieldName("price"); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + val.setValidatorContext(context); + + val.setMaxInclusive("9.95"); + val.validate(prod); // should pass + assertTrue(!context.hasErrors()); + assertEquals("9.95", val.getMaxInclusive()); + + val.setMaxExclusive("9.95"); + val.validate(prod); // should not pass + assertTrue(context.hasErrors()); + assertEquals("9.95", val.getMaxExclusive()); + } + + public void testEdgeOfMinRange() throws Exception { + MyTestProduct prod = new MyTestProduct(); + prod.setName("coca cola"); + prod.setPrice(9.95); + + ValueStack stack = ActionContext.getContext().getValueStack(); + stack.push(prod); + ActionContext.getContext().setValueStack(stack); + + val.setFieldName("price"); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + val.setValidatorContext(context); + + val.setMinInclusive("9.95"); + val.validate(prod); // should pass + assertTrue(!context.hasErrors()); + + val.setMinExclusive("9.95"); + val.validate(prod); // should not pass + assertTrue(context.hasErrors()); + } + + public void testNoValue() throws Exception { + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + val.setFieldName("price"); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + val.setValidatorContext(context); + + val.setMinInclusive("9.95"); + val.validate(null); + assertTrue(!context.hasErrors()); // should pass as null value passed in + } + + @Override + protected void setUp() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-default.xml"), new MockConfigurationProvider()); + val = new DoubleRangeFieldValidator(); + val.setValueStack(ActionContext.getContext().getValueStack()); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + val = null; + } + + private class MyTestProduct { + private double price; + private Double volume; + private String name; + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getVolume() { + return volume; + } + + public void setVolume(Double volume) { + this.volume = volume; + } + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/EmailValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/EmailValidatorTest.java new file mode 100644 index 000000000..b0d10f336 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/EmailValidatorTest.java @@ -0,0 +1,69 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.validator.validators.EmailValidator; + +/** + * Test case for Email Validator + * + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class EmailValidatorTest extends XWorkTestCase { + + public void testEmailValidity() throws Exception { + assertTrue(verifyEmailValidity("tmjee@yahoo.com")); + assertTrue(verifyEmailValidity("tm_jee@yahoo.co")); + assertTrue(verifyEmailValidity("tm.jee@yahoo.co.uk")); + assertTrue(verifyEmailValidity("tm.jee@yahoo.co.biz")); + assertTrue(verifyEmailValidity("tm_jee@yahoo.com")); + assertTrue(verifyEmailValidity("tm_jee@yahoo.net")); + assertTrue(verifyEmailValidity(" user@subname1.subname2.subname3.domainname.co.uk ")); + assertTrue(verifyEmailValidity("tm.j'ee@yahoo.co.uk")); + assertTrue(verifyEmailValidity("tm.j'e.e'@yahoo.co.uk")); + assertTrue(verifyEmailValidity("tmj'ee@yahoo.com")); + + assertFalse(verifyEmailValidity("tm_jee#marry@yahoo.co.uk")); + assertFalse(verifyEmailValidity("tm_jee@ yahoo.co.uk")); + assertFalse(verifyEmailValidity("tm_jee @yahoo.co.uk")); + assertFalse(verifyEmailValidity("tm_j ee @yah oo.co.uk")); + assertFalse(verifyEmailValidity("tm_jee @yah oo.co.uk")); + assertFalse(verifyEmailValidity("tm_jee @ yahoo.com")); + assertFalse(verifyEmailValidity(" user@subname1.subname2.subname3.domainn#ame.co.uk ")); + } + + protected boolean verifyEmailValidity(final String email) throws Exception { + ActionSupport action = new ActionSupport() { + public String getMyEmail() { + return email; + } + }; + + EmailValidator validator = new EmailValidator(); + validator.setValidatorContext(new DelegatingValidatorContext(action)); + validator.setFieldName("myEmail"); + validator.setDefaultMessage("invalid email"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(action); + + return (action.getFieldErrors().size() == 0); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ExpressionValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ExpressionValidatorTest.java new file mode 100644 index 000000000..991650e79 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ExpressionValidatorTest.java @@ -0,0 +1,140 @@ +/* + * 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.validator; + +import com.mockobjects.dynamic.C; +import com.mockobjects.dynamic.Mock; +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.validator.validators.ExpressionValidator; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.easymock.EasyMock; + +/** + * Unit test for ExpressionValidator. + * + * @author Jason Carreira + * @author Claus Ibsen + */ +public class ExpressionValidatorTest extends XWorkTestCase { + + public void testExpressionValidationOfStringLength() throws ValidationException { + TestBean bean = new TestBean(); + bean.setName("abc"); + ActionContext.getContext().getValueStack().push(bean); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(bean, "expressionValidation", context); + assertTrue(context.hasFieldErrors()); + + final Map fieldErrors = context.getFieldErrors(); + assertTrue(fieldErrors.containsKey("name")); + + List nameErrors = (List) fieldErrors.get("name"); + assertEquals(1, nameErrors.size()); + assertEquals("Name must be greater than 5 characters, it is currently 'abc'", nameErrors.get(0)); + + bean.setName("abcdefg"); + context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(bean, "expressionValidation", context); + assertFalse(context.hasFieldErrors()); + } + + public void testExpressionValidatorFailure() throws Exception { + HashMap params = new HashMap(); + params.put("date", "12/23/2002"); + params.put("foo", "5"); + params.put("bar", "7"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasActionErrors()); + + Collection errors = ((ValidationAware) proxy.getAction()).getActionErrors(); + assertEquals(1, errors.size()); + + String message = (String) errors.iterator().next(); + assertNotNull(message); + assertEquals("Foo must be greater than Bar. Foo = 5, Bar = 7.", message); + } + + public void testExpressionValidatorSuccess() throws Exception { + HashMap params = new HashMap(); + + //make it not fail + params.put("date", "12/23/2002"); + params.put("foo", "10"); + params.put("bar", "7"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertFalse(((ValidationAware) proxy.getAction()).hasActionErrors()); + } + + public void testGetSetExpresion() { + ExpressionValidator ev = new ExpressionValidator(); + ev.setExpression("{top}"); + assertEquals("{top}", ev.getExpression()); + } + + public void testNoBooleanExpression() throws Exception { + Mock mock = new Mock(ValidationAware.class); + mock.expect("addActionError", C.ANY_ARGS); + + ExpressionValidator ev = new ExpressionValidator(); + ev.setValidatorContext(new DelegatingValidatorContext(mock.proxy())); + ev.setExpression("{top}"); + ev.setValueStack(ActionContext.getContext().getValueStack()); + ev.validate("Hello"); // {top} will evalute to Hello that is not a Boolean + mock.verify(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + loadConfigurationProviders(new MockConfigurationProvider()); + + ActionConfig config = new ActionConfig.Builder("", "name", "").build(); + ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class); + ActionProxy proxy = EasyMock.createNiceMock(ActionProxy.class); + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(null).anyTimes(); + EasyMock.expect(invocation.invoke()).andReturn(Action.SUCCESS).anyTimes(); + EasyMock.expect(proxy.getMethod()).andReturn("execute").anyTimes(); + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(proxy); + + ActionContext.getContext().setActionInvocation(invocation); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/GenericValidatorContext.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/GenericValidatorContext.java new file mode 100644 index 000000000..9a98628bd --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/GenericValidatorContext.java @@ -0,0 +1,143 @@ +/* + * 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.validator; + +import java.util.*; + + +/** + * Dummy validator context to use to capture error messages. + * + * @author Mark Woon + * @author Matthew Payne + */ +public class GenericValidatorContext extends DelegatingValidatorContext { + + private Collection actionErrors; + private Collection actionMessages; + private Map> fieldErrors; + + + public GenericValidatorContext(Object object) { + super(object); + } + + + @Override + public synchronized void setActionErrors(Collection errorMessages) { + this.actionErrors = errorMessages; + } + + @Override + public synchronized Collection getActionErrors() { + return new ArrayList(internalGetActionErrors()); + } + + @Override + public synchronized void setActionMessages(Collection messages) { + this.actionMessages = messages; + } + + @Override + public synchronized Collection getActionMessages() { + return new ArrayList(internalGetActionMessages()); + } + + @Override + public synchronized void setFieldErrors(Map> errorMap) { + this.fieldErrors = errorMap; + } + + /** + * Get the field specific errors. + * + * @return an unmodifiable Map with errors mapped from fieldname (String) to Collection of String error messages + */ + @Override + public synchronized Map> getFieldErrors() { + return new HashMap>(internalGetFieldErrors()); + } + + @Override + public synchronized void addActionError(String anErrorMessage) { + internalGetActionErrors().add(anErrorMessage); + } + + /** + * Add an Action level message to this Action + */ + @Override + public void addActionMessage(String aMessage) { + internalGetActionMessages().add(aMessage); + } + + @Override + public synchronized void addFieldError(String fieldName, String errorMessage) { + final Map> errors = internalGetFieldErrors(); + List thisFieldErrors = errors.get(fieldName); + + if (thisFieldErrors == null) { + thisFieldErrors = new ArrayList(); + errors.put(fieldName, thisFieldErrors); + } + + thisFieldErrors.add(errorMessage); + } + + @Override + public synchronized boolean hasActionErrors() { + return (actionErrors != null) && !actionErrors.isEmpty(); + } + + /** + * Note that this does not have the same meaning as in WW 1.x + * + * @return (hasActionErrors() || hasFieldErrors()) + */ + @Override + public synchronized boolean hasErrors() { + return (hasActionErrors() || hasFieldErrors()); + } + + @Override + public synchronized boolean hasFieldErrors() { + return (fieldErrors != null) && !fieldErrors.isEmpty(); + } + + private Collection internalGetActionErrors() { + if (actionErrors == null) { + actionErrors = new ArrayList(); + } + + return actionErrors; + } + + private Collection internalGetActionMessages() { + if (actionMessages == null) { + actionMessages = new ArrayList(); + } + + return actionMessages; + } + + private Map> internalGetFieldErrors() { + if (fieldErrors == null) { + fieldErrors = new HashMap>(); + } + + return fieldErrors; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/IntRangeValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/IntRangeValidatorTest.java new file mode 100644 index 000000000..b51ba8552 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/IntRangeValidatorTest.java @@ -0,0 +1,67 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * IntRangeValidatorTest + *

+ * Created : Jan 21, 2003 12:16:01 AM + * + * @author Jason Carreira + */ +public class IntRangeValidatorTest extends XWorkTestCase { + + public void testRangeValidation() { + HashMap params = new HashMap(); + params.put("bar", "5"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List errorMessages = errors.get("bar"); + assertEquals(1, errorMessages.size()); + + String errorMessage = errorMessages.get(0); + assertNotNull(errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override + protected void setUp() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/LongRangeValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/LongRangeValidatorTest.java new file mode 100644 index 000000000..5875821eb --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/LongRangeValidatorTest.java @@ -0,0 +1,65 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * LongRangeValidatorTest + *

+ * + */ +public class LongRangeValidatorTest extends XWorkTestCase { + + public void testRangeValidation() { + HashMap params = new HashMap(); + params.put("longFoo", "200"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List errorMessages = (List) errors.get("longFoo"); + assertEquals(1, errorMessages.size()); + + String errorMessage = (String) errorMessages.get(0); + assertNotNull(errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override + protected void setUp() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ModelDrivenValidationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ModelDrivenValidationTest.java new file mode 100644 index 000000000..0b9e43734 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ModelDrivenValidationTest.java @@ -0,0 +1,51 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * ModelDrivenValidationTest + * + * @author Jason Carreira + * Created Oct 1, 2003 10:08:25 AM + */ +public class ModelDrivenValidationTest extends XWorkTestCase { + + public void testModelDrivenValidation() throws Exception { + Map params = new HashMap(); + params.put("count", new String[]{"11"}); + + Map context = new HashMap(); + context.put(ActionContext.PARAMETERS, params); + + loadConfigurationProviders(new XmlConfigurationProvider("xwork-sample.xml")); + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "TestModelDrivenValidation", context); + assertEquals(Action.SUCCESS, proxy.execute()); + + ModelDrivenAction action = (ModelDrivenAction) proxy.getAction(); + assertTrue(action.hasFieldErrors()); + assertTrue(action.getFieldErrors().containsKey("count")); + assertEquals("count must be between 1 and 10, current value is 11.", ((List) action.getFieldErrors().get("count")).get(0)); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/RegexFieldValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/RegexFieldValidatorTest.java new file mode 100644 index 000000000..479d125ac --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/RegexFieldValidatorTest.java @@ -0,0 +1,195 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.validators.RegexFieldValidator; + +import java.util.List; + +/** + * Unit test for RegexFieldValidator. + *

+ * This unit test is only to test that the regex field validator works, not to + * unit test the build in reg.exp from JDK. That is why the expressions are so simple. + * + * @author Claus Ibsen + */ +public class RegexFieldValidatorTest extends XWorkTestCase { + + public void testMatch() throws Exception { + MyTestPerson testPerson = new MyTestPerson(); + testPerson.setUsername("Secret"); + + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setExpression("^Sec.*"); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("username"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(testPerson); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testMatchNoTrim() throws Exception { + MyTestPerson testPerson = new MyTestPerson(); + testPerson.setUsername("Secret "); // must end with one whitespace + + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setTrim(false); + validator.setExpression("^Sec.*\\s"); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("username"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(testPerson); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testFail() throws Exception { + MyTestPerson testPerson = new MyTestPerson(); + testPerson.setUsername("Superman"); + + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setExpression("^Sec.*"); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("username"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(testPerson); + + assertTrue(validator.getValidatorContext().hasErrors()); + assertTrue(validator.getValidatorContext().hasFieldErrors()); + List msgs = validator.getValidatorContext().getFieldErrors().get("username"); + assertNotNull(msgs); + assertTrue(msgs.size() == 1); // should contain 1 error message + + // when failing the validator will not add action errors/msg + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + } + + public void testNoFieldName() throws Exception { + MyTestPerson testPerson = new MyTestPerson(); + testPerson.setUsername("NoExpression"); + + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setExpression("^Sec.*"); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName(null); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(testPerson); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testGetExpression() throws Exception { + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setExpression("^Hello.*"); + assertEquals("^Hello.*", validator.getExpression()); + } + + public void testIsTrimmed() throws Exception { + RegexFieldValidator validator = new RegexFieldValidator(); + assertEquals(true, validator.isTrimed()); + validator.setTrim(false); + assertEquals(false, validator.isTrimed()); + } + + public void testEmptyName() throws Exception { + MyTestPerson testPerson = new MyTestPerson(); + testPerson.setUsername(""); + + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setExpression("^Sec.*"); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("username"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(testPerson); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testNoStringField() throws Exception { + MyTestPerson testPerson = new MyTestPerson(); + testPerson.setAge(33); + + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.getContext().setValueStack(stack); + + RegexFieldValidator validator = new RegexFieldValidator(); + validator.setExpression("[0-9][0-9]"); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("age"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(testPerson); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + private class MyTestPerson { + private String username; + private int age; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/RepopulateConversionErrorFieldValidatorSupportTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/RepopulateConversionErrorFieldValidatorSupportTest.java new file mode 100644 index 000000000..7415b1045 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/RepopulateConversionErrorFieldValidatorSupportTest.java @@ -0,0 +1,129 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.validators.RepopulateConversionErrorFieldValidatorSupport; + +import java.util.Map; + +/** + * Test RepopulateConversionErrorFieldValidatorSupport. + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class RepopulateConversionErrorFieldValidatorSupportTest extends XWorkTestCase { + + + InternalRepopulateConversionErrorFieldValidatorSupport validator1; + InternalRepopulateConversionErrorFieldValidatorSupport validator2; + ActionSupport action; + + public void testUseFullFieldName() throws Exception { + validator2.setRepopulateField("true"); + validator2.validate(action); + + ActionContext.getContext().getActionInvocation().invoke(); + Object valueFromStack1 = ActionContext.getContext().getValueStack().findValue("someFieldName", String.class); + Object valueFromStack2 = ActionContext.getContext().getValueStack().findValue("xxxsomeFieldName", String.class); + + assertNull(valueFromStack1); + assertEquals(valueFromStack2, "some value"); + } + + public void testGetterSetterGetsCalledApropriately1() throws Exception { + + validator1.setRepopulateField("true"); + validator1.validate(action); + + + ActionContext.getContext().getActionInvocation().invoke(); + + Object valueFromStack = ActionContext.getContext().getValueStack().findValue("someFieldName", String.class); + + assertEquals(valueFromStack, "some value"); + } + + + public void testGetterSetterGetsCalledApropriately2() throws Exception { + + validator1.setRepopulateField("false"); + validator1.validate(action); + + + ActionContext.getContext().getActionInvocation().invoke(); + + Object valueFromStack = ActionContext.getContext().getValueStack().findValue("someFieldName", String.class); + + assertEquals(valueFromStack, null); + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + ValueStack stack = ActionContext.getContext().getValueStack(); + MockActionInvocation invocation = new MockActionInvocation(); + invocation.setStack(stack); + ActionContext.getContext().setValueStack(stack); + ActionContext.getContext().setActionInvocation(invocation); + + String[] conversionErrorValue = new String[] { "some value" }; + Map conversionErrors = ActionContext.getContext().getConversionErrors(); + conversionErrors.put("someFieldName", conversionErrorValue); + conversionErrors.put("xxxsomeFieldName", conversionErrorValue); + + action = new ActionSupport(); + validator1 = + new InternalRepopulateConversionErrorFieldValidatorSupport(); + validator1.setFieldName("someFieldName"); + validator1.setValidatorContext(new DelegatingValidatorContext(action)); + + validator2 = + new InternalRepopulateConversionErrorFieldValidatorSupport(); + validator2.setFieldName("someFieldName"); + validator2.setValidatorContext(new DelegatingValidatorContext(action) { + @Override + public String getFullFieldName(String fieldName) { + return "xxx"+fieldName; + } + }); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + validator1 = null; + action = null; + } + + + // === inner class ============ + + class InternalRepopulateConversionErrorFieldValidatorSupport extends RepopulateConversionErrorFieldValidatorSupport { + public boolean doValidateGetsCalled = false; + + @Override + protected void doValidate(Object object) throws ValidationException { + doValidateGetsCalled = true; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ShortRangeValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ShortRangeValidatorTest.java new file mode 100644 index 000000000..146dc0e7f --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ShortRangeValidatorTest.java @@ -0,0 +1,65 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * ShortRangeValidatorTest + *

+ * + */ +public class ShortRangeValidatorTest extends XWorkTestCase { + + public void testRangeValidation() { + HashMap params = new HashMap(); + params.put("shortFoo", "200"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List errorMessages = (List) errors.get("shortFoo"); + assertEquals(1, errorMessages.size()); + + String errorMessage = (String) errorMessages.get(0); + assertNotNull(errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override + protected void setUp() throws Exception { + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider()); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java new file mode 100644 index 000000000..0f074f621 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java @@ -0,0 +1,237 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.validators.ValidatorSupport; + +import java.util.*; + + +/** + * SimpleActionValidationTest + *

+ * Created : Jan 20, 2003 11:04:25 PM + * + * @author Jason Carreira + */ +public class SimpleActionValidationTest extends XWorkTestCase { + + private Locale origLocale; + + + public void testAliasValidation() { + HashMap params = new HashMap(); + params.put("baz", "10"); + + //valid values + params.put("bar", "7"); + params.put("date", "12/23/2002"); + params.put("percentage", "1.23456789"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + + ValidationAware validationAware = (ValidationAware) proxy.getAction(); + assertFalse(validationAware.hasFieldErrors()); + + // put in an out-of-range value to see if the old validators still work + ActionContext.setContext(new ActionContext(new HashMap())); + params.put("bar", "42"); + proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ALIAS_NAME, extraContext); + proxy.execute(); + validationAware = (ValidationAware) proxy.getAction(); + assertTrue(validationAware.hasFieldErrors()); + + Map> errors = validationAware.getFieldErrors(); + assertTrue(errors.containsKey("baz")); + + List bazErrors = errors.get("baz"); + assertEquals(1, bazErrors.size()); + + String message = bazErrors.get(0); + assertEquals("baz out of range.", message); + assertTrue(errors.containsKey("bar")); + + List barErrors = errors.get("bar"); + assertEquals(1, barErrors.size()); + message = barErrors.get(0); + assertEquals("bar must be between 6 and 10, current value is 42.", message); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testLookingUpFieldNameAsTextKey() { + HashMap params = new HashMap(); + + // should cause a message + params.put("baz", "-1"); + + //valid values + params.put("bar", "7"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List bazErrors = errors.get("baz"); + assertEquals(1, bazErrors.size()); + + String errorMessage = bazErrors.get(0); + assertNotNull(errorMessage); + assertEquals("Baz Field must be greater than 0", errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testMessageKey() { + HashMap params = new HashMap(); + params.put("foo", "200"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + ValueStack stack = ActionContext.getContext().getValueStack(); + ActionContext.setContext(new ActionContext(stack.getContext())); + ActionContext.getContext().setLocale(Locale.US); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List fooErrors = errors.get("foo"); + assertEquals(1, fooErrors.size()); + + String errorMessage = fooErrors.get(0); + assertNotNull(errorMessage); + assertEquals("Foo Range Message", errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testMessageKeyIsReturnedIfNoOtherDefault() throws ValidationException { + Validator validator = new ValidatorSupport() { + public void validate(Object object) throws ValidationException { + addActionError(object); + } + }; + validator.setValueStack(ActionContext.getContext().getValueStack()); + + String messageKey = "does.not.exist"; + validator.setMessageKey(messageKey); + + ValidatorContext validatorContext = new DelegatingValidatorContext(new SimpleAction()); + validator.setValidatorContext(validatorContext); + validator.validate(this); + assertTrue(validatorContext.hasActionErrors()); + + Collection errors = validatorContext.getActionErrors(); + assertEquals(1, errors.size()); + assertEquals(messageKey, errors.toArray()[0]); + } + + public void testParamterizedMessage() { + HashMap params = new HashMap(); + params.put("bar", "42"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List barErrors = errors.get("bar"); + assertEquals(1, barErrors.size()); + + String errorMessage = barErrors.get(0); + assertNotNull(errorMessage); + assertEquals("bar must be between 6 and 10, current value is 42.", errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public void testSubPropertiesAreValidated() { + HashMap params = new HashMap(); + params.put("baz", "10"); + + //valid values + params.put("foo", "8"); + params.put("bar", "7"); + params.put("date", "12/23/2002"); + + params.put("bean.name", "Name should be valid"); + + // this should cause a message + params.put("bean.count", "100"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + try { + ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_SUBPROPERTY_NAME, extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasFieldErrors()); + + Map> errors = ((ValidationAware) proxy.getAction()).getFieldErrors(); + List beanCountErrors = errors.get("bean.count"); + assertEquals(1, beanCountErrors.size()); + + String errorMessage = beanCountErrors.get(0); + assertNotNull(errorMessage); + assertEquals("bean.count out of range.", errorMessage); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Override + protected void setUp() throws Exception { + origLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider()); + } + + @Override + protected void tearDown() throws Exception { + Locale.setDefault(origLocale); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/StringLengthFieldValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/StringLengthFieldValidatorTest.java new file mode 100644 index 000000000..fc15056b9 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/StringLengthFieldValidatorTest.java @@ -0,0 +1,154 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator; + +/** + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class StringLengthFieldValidatorTest extends XWorkTestCase { + + protected InternalActionSupport action; + protected StringLengthFieldValidator validator; + + public void testStringLengthEmptyNoTrim1() throws Exception { + action.setMyField(""); + + validator.setTrim(false); + validator.validate(action); + + assertEquals(action.getMyField(), ""); + assertFalse(action.hasFieldErrors()); + } + + public void testStringLengthNullNoTrim() throws Exception { + action.setMyField(null); + + validator.setTrim(false); + validator.validate(action); + + assertEquals(action.getMyField(), null); + assertFalse(action.hasFieldErrors()); + } + + public void testStringLengthEmptyTrim1() throws Exception { + action.setMyField(" "); + + validator.setTrim(true); + validator.validate(action); + + assertEquals(action.getMyField(), " "); + assertFalse(action.hasFieldErrors()); + } + + public void testStringLengthEmptyNoTrim2() throws Exception { + action.setMyField(" "); + + validator.setTrim(false); + validator.validate(action); + + assertEquals(action.getMyField(), " "); + assertTrue(action.hasFieldErrors()); + } + + + public void testStringLengthNullTrim() throws Exception { + action.setMyField(null); + + validator.setTrim(true); + validator.validate(action); + + assertEquals(action.getMyField(), null); + assertFalse(action.hasFieldErrors()); + } + + public void testInvalidStringLengthNoTrim() throws Exception { + action.setMyField("abcdefghijklmn"); + + validator.setTrim(false); + validator.validate(action); + + assertEquals(action.getMyField(), "abcdefghijklmn"); + assertTrue(action.hasFieldErrors()); + } + + public void testInvalidStringLengthTrim() throws Exception { + action.setMyField("abcdefghijklmn "); + + validator.setTrim(true); + validator.validate(action); + + assertEquals(action.getMyField(), "abcdefghijklmn "); + assertTrue(action.hasFieldErrors()); + } + + public void testValidStringLengthNoTrim() throws Exception { + action.setMyField(" "); + + validator.setTrim(false); + validator.validate(action); + + assertEquals(action.getMyField(), " "); + assertFalse(action.hasFieldErrors()); + } + + public void testValidStringLengthTrim() throws Exception { + action.setMyField("asd "); + + validator.setTrim(true); + validator.validate(action); + + assertEquals(action.getMyField(), "asd "); + assertFalse(action.hasFieldErrors()); + } + + + @Override + protected void setUp() throws Exception { + super.setUp(); + action = new InternalActionSupport(); + validator = new StringLengthFieldValidator(); + validator.setFieldName("myField"); + validator.setMessageKey("error"); + validator.setValidatorContext(new DelegatingValidatorContext(action)); + validator.setMaxLength(5); + validator.setMinLength(2); + validator.setValueStack(ActionContext.getContext().getValueStack()); + } + + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + action = null; + validator = null; + } + + public static class InternalActionSupport extends ActionSupport { + + private static final long serialVersionUID = 1L; + + private String myField; + public String getMyField() { return this.myField; } + public void setMyField(String myField) { this.myField = myField; } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/StringValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/StringValidatorTest.java new file mode 100644 index 000000000..beaf63867 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/StringValidatorTest.java @@ -0,0 +1,213 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.test.Equidae; +import com.opensymphony.xwork2.validator.validators.RequiredStringValidator; + +import java.util.List; +import java.util.Map; + +import org.easymock.EasyMock; + +/** + * @author Mark Woon + * @author tm_jee (tm_jee (at) yahoo.co.uk ) + */ +public class StringValidatorTest extends XWorkTestCase { + + public void testRequiredStringWithNullValue() throws Exception { + Equidae equidae = new Equidae(); + equidae.setHorse(null); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + + assertTrue(context.hasFieldErrors()); + } + + + public void testRequiredString() throws Exception { + Equidae equidae = new Equidae(); + + // everything should fail + equidae.setHorse(""); + ActionContext.getContext().getValueStack().push(equidae); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + + assertTrue(context.hasFieldErrors()); + + Map fieldErrors = context.getFieldErrors(); + assertTrue(fieldErrors.containsKey("horse")); + assertEquals(2, ((List) fieldErrors.get("horse")).size()); + + // trim = false should fail + equidae.setHorse(" "); + ActionContext.getContext().getValueStack().push(equidae); + context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + + assertTrue(context.hasFieldErrors()); + fieldErrors = context.getFieldErrors(); + assertTrue(fieldErrors.containsKey("horse")); + + List errors = (List) fieldErrors.get("horse"); + assertEquals(1, errors.size()); + assertEquals("trim", (String) errors.get(0)); + } + + public void testStringLength() throws Exception { + Equidae equidae = new Equidae(); + + equidae.setCow("asdf"); + equidae.setDonkey("asdf"); + ActionContext.getContext().getValueStack().push(equidae); + + DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + assertTrue(context.hasFieldErrors()); + + Map fieldErrors = context.getFieldErrors(); + + // cow + assertTrue(fieldErrors.containsKey("cow")); + + List errors = (List) fieldErrors.get("cow"); + assertEquals(2, errors.size()); + assertEquals("noTrim-min5", errors.get(0)); + assertEquals("noTrim-min5-max10", errors.get(1)); + + // donkey + assertTrue(fieldErrors.containsKey("donkey")); + errors = (List) fieldErrors.get("donkey"); + assertEquals(2, errors.size()); + assertEquals("trim-min5", errors.get(0)); + assertEquals("trim-min5-max10", errors.get(1)); + + equidae.setCow("asdf "); + equidae.setDonkey("asdf "); + ActionContext.getContext().getValueStack().push(equidae); + context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + assertTrue(context.hasFieldErrors()); + + fieldErrors = context.getFieldErrors(); + + // cow + assertFalse(fieldErrors.containsKey("cow")); + + // donkey + assertTrue(fieldErrors.containsKey("donkey")); + errors = (List) fieldErrors.get("donkey"); + assertEquals(2, errors.size()); + assertEquals("trim-min5", errors.get(0)); + assertEquals("trim-min5-max10", errors.get(1)); + + equidae.setCow("asdfasdf"); + equidae.setDonkey("asdfasdf"); + ActionContext.getContext().getValueStack().push(equidae); + context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + assertTrue(context.hasFieldErrors()); + + fieldErrors = context.getFieldErrors(); + + // cow + assertFalse(fieldErrors.containsKey("cow")); + + // donkey + assertFalse(fieldErrors.containsKey("donkey")); + + equidae.setCow("asdfasdf "); + equidae.setDonkey("asdfasdf "); + ActionContext.getContext().getValueStack().push(equidae); + context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + assertTrue(context.hasFieldErrors()); + + fieldErrors = context.getFieldErrors(); + + // cow + assertTrue(fieldErrors.containsKey("cow")); + errors = (List) fieldErrors.get("cow"); + assertEquals(2, errors.size()); + assertEquals("noTrim-min5-max10", errors.get(0)); + assertEquals("noTrim-max10", errors.get(1)); + + // donkey + assertFalse(fieldErrors.containsKey("donkey")); + + equidae.setCow("asdfasdfasdf"); + equidae.setDonkey("asdfasdfasdf"); + ActionContext.getContext().getValueStack().push(equidae); + context = new DelegatingValidatorContext(new ValidationAwareSupport()); + container.getInstance(ActionValidatorManager.class).validate(equidae, null, context); + assertTrue(context.hasFieldErrors()); + + fieldErrors = context.getFieldErrors(); + + // cow + assertTrue(fieldErrors.containsKey("cow")); + errors = (List) fieldErrors.get("cow"); + assertEquals(2, errors.size()); + assertEquals("noTrim-min5-max10", errors.get(0)); + assertEquals("noTrim-max10", errors.get(1)); + + // donkey + assertTrue(fieldErrors.containsKey("donkey")); + errors = (List) fieldErrors.get("donkey"); + assertEquals(2, errors.size()); + assertEquals("trim-min5-max10", errors.get(0)); + assertEquals("trim-max10", errors.get(1)); + } + + public void testGetSetTrim() { + RequiredStringValidator val = new RequiredStringValidator(); + + val.setTrim(true); + assertEquals(true, val.getTrim()); + + val.setTrim(false); + assertEquals(false, val.getTrim()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + loadConfigurationProviders(new MockConfigurationProvider()); + + ActionConfig config = new ActionConfig.Builder("", "name", "").build(); + ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class); + ActionProxy proxy = EasyMock.createNiceMock(ActionProxy.class); + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(null).anyTimes(); + EasyMock.expect(invocation.invoke()).andReturn(Action.SUCCESS).anyTimes(); + EasyMock.expect(proxy.getMethod()).andReturn("execute").anyTimes(); + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(proxy); + + ActionContext.getContext().setActionInvocation(invocation); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/URLValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/URLValidatorTest.java new file mode 100644 index 000000000..97248952e --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/URLValidatorTest.java @@ -0,0 +1,143 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.validator.validators.URLValidator; + +/** + * Test case for URLValidator + * + * @author tm_jee + * @version $Date$ $Id$ + */ +public class URLValidatorTest extends XWorkTestCase { + + + ValueStack stack; + ActionContext actionContext; + + public void testAcceptNullValueForMutualExclusionOfValidators() throws Exception { + + URLValidator validator = new URLValidator(); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("testingUrl1"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(new MyObject()); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testInvalidEmptyValue() throws Exception { + + URLValidator validator = new URLValidator(); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("testingUrl2"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(new MyObject()); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testInvalidValue() throws Exception { + + URLValidator validator = new URLValidator(); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("testingUrl3"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(new MyObject()); + + assertTrue(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertTrue(validator.getValidatorContext().hasFieldErrors()); + } + + + public void testValidUrl1() throws Exception { + + URLValidator validator = new URLValidator(); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("testingUrl4"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(new MyObject()); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + public void testValidUrl2() throws Exception { + + URLValidator validator = new URLValidator(); + validator.setValidatorContext(new GenericValidatorContext(new Object())); + validator.setFieldName("testingUrl5"); + validator.setValueStack(ActionContext.getContext().getValueStack()); + validator.validate(new MyObject()); + + assertFalse(validator.getValidatorContext().hasErrors()); + assertFalse(validator.getValidatorContext().hasActionErrors()); + assertFalse(validator.getValidatorContext().hasActionMessages()); + assertFalse(validator.getValidatorContext().hasFieldErrors()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + stack = ActionContext.getContext().getValueStack(); + actionContext = ActionContext.getContext(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + stack = null; + actionContext = null; + } + + + class MyObject { + public String getTestingUrl1() { + return null; + } + + public String getTestingUrl2() { + return ""; + } + + public String getTestingUrl3() { + return "sasdasd@asddd"; + } + + public String getTestingUrl4() { + //return "http://yahoo.com/"; + return "http://www.jroller.com1?qwe=qwe"; + } + + public String getTestingUrl5() { + return "http://yahoo.com/articles?id=123"; + } + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ValidatorAnnotationTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ValidatorAnnotationTest.java new file mode 100644 index 000000000..9676618fa --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/ValidatorAnnotationTest.java @@ -0,0 +1,116 @@ +package com.opensymphony.xwork2.validator; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; + +import java.util.Collection; +import java.util.HashMap; + +/** + * Unit test for annotated Validators. + * + * @author Rainer Hermanns + */ +public class ValidatorAnnotationTest extends XWorkTestCase { + + public void testNotAnnotatedMethodSuccess() throws Exception { + HashMap params = new HashMap(); + params.put("date", "12/23/2002"); + params.put("foo", "5"); + params.put("bar", "7"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", "notAnnotatedMethod", extraContext); + proxy.execute(); + assertFalse(((ValidationAware) proxy.getAction()).hasActionErrors()); + + Collection errors = ((ValidationAware) proxy.getAction()).getActionErrors(); + assertEquals(0, errors.size()); + } + + public void testNotAnnotatedMethodSuccess2() throws Exception { + HashMap params = new HashMap(); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", "notAnnotatedMethod", extraContext); + proxy.execute(); + assertFalse(((ValidationAware) proxy.getAction()).hasActionErrors()); + + Collection errors = ((ValidationAware) proxy.getAction()).getActionErrors(); + assertEquals(0, errors.size()); + } + + public void testAnnotatedMethodFailure() throws Exception { + HashMap params = new HashMap(); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", "annotatedMethod", extraContext); + proxy.execute(); + assertTrue(((ValidationAware) proxy.getAction()).hasActionErrors()); + Collection errors = ((ValidationAware) proxy.getAction()).getActionErrors(); + assertEquals(1, errors.size()); + + assertEquals("Need param1 or param2.", errors.iterator().next()); + + } + + public void testAnnotatedMethodSuccess() throws Exception { + HashMap params = new HashMap(); + + //make it not fail + params.put("param1", "key1"); + params.put("param2", "key2"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", "annotatedMethod", extraContext); + proxy.execute(); + assertFalse(((ValidationAware) proxy.getAction()).hasActionErrors()); + } + + public void testAnnotatedMethodSuccess2() throws Exception { + HashMap params = new HashMap(); + + //make it not fail + params.put("param2", "key2"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", "annotatedMethod", extraContext); + proxy.execute(); + assertFalse(((ValidationAware) proxy.getAction()).hasActionErrors()); + } + + public void testAnnotatedMethodSuccess3() throws Exception { + HashMap params = new HashMap(); + + //make it not fail + params.put("param1", "key1"); + + HashMap extraContext = new HashMap(); + extraContext.put(ActionContext.PARAMETERS, params); + + ActionProxy proxy = actionProxyFactory.createActionProxy("", "annotatedMethod", extraContext); + proxy.execute(); + assertFalse(((ValidationAware) proxy.getAction()).hasActionErrors()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + loadConfigurationProviders(new XmlConfigurationProvider("xwork-default.xml"), new XmlConfigurationProvider("xwork-test-validation.xml")); + } + +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorFieldValidatorModelTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorFieldValidatorModelTest.java new file mode 100644 index 000000000..34a780652 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorFieldValidatorModelTest.java @@ -0,0 +1,130 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.test.TestBean2; + +import java.util.*; + +import org.easymock.EasyMock; + + +/** + * VisitorFieldValidatorModelTest + * + * @author Jason Carreira + * Date: Mar 18, 2004 2:51:42 PM + */ +public class VisitorFieldValidatorModelTest extends XWorkTestCase { + + protected VisitorValidatorModelAction action; + private Locale origLocale; + + + @Override + public void setUp() throws Exception { + super.setUp(); + origLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + action = new VisitorValidatorModelAction(); + + TestBean bean = action.getBean(); + Calendar cal = new GregorianCalendar(1900, 01, 01); + bean.setBirth(cal.getTime()); + bean.setCount(-1); + + ActionConfig config = new ActionConfig.Builder("", "name", "").build(); + ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class); + ActionProxy proxy = EasyMock.createNiceMock(ActionProxy.class); + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(null).anyTimes(); + EasyMock.expect(invocation.invoke()).andReturn(Action.SUCCESS).anyTimes(); + EasyMock.expect(proxy.getMethod()).andReturn("execute").anyTimes(); + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(proxy); + + ActionContext.getContext().setActionInvocation(invocation); + + } + + public void testModelFieldErrorsAddedWithoutFieldPrefix() throws Exception { + container.getInstance(ActionValidatorManager.class).validate(action, null); + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + + // the required string validation inherited from the VisitorValidatorTestAction + assertTrue(fieldErrors.containsKey("context")); + + // the bean validation which is now at the top level because we set the appendPrefix to false + assertTrue(fieldErrors.containsKey("name")); + + List nameMessages = fieldErrors.get("name"); + assertEquals(1, nameMessages.size()); + + String nameMessage = (String) nameMessages.get(0); + assertEquals("You must enter a name.", nameMessage); + } + + public void testModelFieldErrorsAddedWithoutFieldPrefixForInterface() throws Exception { + TestBean origBean = action.getBean(); + TestBean2 bean = new TestBean2(); + bean.setBirth(origBean.getBirth()); + bean.setCount(origBean.getCount()); + action.setBean(bean); + assertTrue(action.getBean() instanceof TestBean2); + + container.getInstance(ActionValidatorManager.class).validate(action, null); + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + + // the required string validation inherited from the VisitorValidatorTestAction + assertTrue(fieldErrors.containsKey("context")); + + // the bean validation which is now at the top level because we set the appendPrefix to false + assertTrue(fieldErrors.containsKey("name")); + + List nameMessages = fieldErrors.get("name"); + assertEquals(1, nameMessages.size()); + + String nameMessage = nameMessages.get(0); + assertEquals("You must enter a name.", nameMessage); + + // should also have picked up validation check for DataAware interface + assertTrue(fieldErrors.containsKey("data")); + + List dataMessages = fieldErrors.get("data"); + assertEquals(1, dataMessages.size()); + + String dataMessage = dataMessages.get(0); + assertEquals("You must enter a value for data.", dataMessage); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + ActionContext.setContext(null); + Locale.setDefault(origLocale); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorFieldValidatorTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorFieldValidatorTest.java new file mode 100644 index 000000000..8f9acd1bb --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorFieldValidatorTest.java @@ -0,0 +1,195 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor; + +import java.util.*; + +import org.easymock.EasyMock; +import org.easymock.IAnswer; + + +/** + * VisitorFieldValidatorTest + * + * @author Jason Carreira + * Created Aug 4, 2003 1:26:01 AM + */ +public class VisitorFieldValidatorTest extends XWorkTestCase { + + protected VisitorValidatorTestAction action; + private Locale origLocale; + + + @Override + public void setUp() throws Exception { + super.setUp(); + origLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + + action = new VisitorValidatorTestAction(); + + TestBean bean = action.getBean(); + Calendar cal = new GregorianCalendar(1900, 01, 01); + bean.setBirth(cal.getTime()); + bean.setCount(-1); + + ActionConfig config = new ActionConfig.Builder("", "name", "").build(); + ActionInvocation invocation = EasyMock.createNiceMock(ActionInvocation.class); + ActionProxy proxy = EasyMock.createNiceMock(ActionProxy.class); + + EasyMock.expect(invocation.getProxy()).andReturn(proxy).anyTimes(); + EasyMock.expect(invocation.getAction()).andReturn(action).anyTimes(); + EasyMock.expect(invocation.invoke()).andReturn(Action.SUCCESS).anyTimes(); + EasyMock.expect(proxy.getMethod()).andReturn("execute").anyTimes(); + EasyMock.expect(proxy.getConfig()).andReturn(config).anyTimes(); + + + EasyMock.replay(invocation); + EasyMock.replay(proxy); + + ActionContext.getContext().setActionInvocation(invocation); + } + + public void testArrayValidation() throws Exception { + TestBean[] beanArray = action.getTestBeanArray(); + TestBean testBean = beanArray[0]; + testBean.setName("foo"); + validate("validateArray"); + + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + + //4 errors for the array, one for context + assertEquals(5, fieldErrors.size()); + assertTrue(fieldErrors.containsKey("testBeanArray[1].name")); + + //the error from the action should be there too + assertTrue(fieldErrors.containsKey("context")); + + List errors = fieldErrors.get("testBeanArray[1].name"); + assertEquals(1, errors.size()); + errors = fieldErrors.get("testBeanArray[2].name"); + assertEquals(1, errors.size()); + errors = fieldErrors.get("testBeanArray[3].name"); + assertEquals(1, errors.size()); + errors = fieldErrors.get("testBeanArray[4].name"); + assertEquals(1, errors.size()); + } + + public void testBeanMessagesUseBeanResourceBundle() throws Exception { + validate("beanMessageBundle"); + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + assertTrue(fieldErrors.containsKey("bean.count")); + + List beanCountMessages = fieldErrors.get("bean.count"); + assertEquals(1, beanCountMessages.size()); + + String beanCountMessage = beanCountMessages.get(0); + assertEquals("bean: Count must be between 1 and 100, current value is -1.", beanCountMessage); + } + + public void testCollectionValidation() throws Exception { + List testBeanList = action.getTestBeanList(); + TestBean testBean = (TestBean) testBeanList.get(0); + testBean.setName("foo"); + validate("validateList"); + + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + + //4 for the list, 1 for context + assertEquals(5, fieldErrors.size()); + assertTrue(fieldErrors.containsKey("testBeanList[1].name")); + + //the error from the action should be there too + assertTrue(fieldErrors.containsKey("context")); + + List errors = fieldErrors.get("testBeanList[1].name"); + assertEquals(1, errors.size()); + errors = fieldErrors.get("testBeanList[2].name"); + assertEquals(1, errors.size()); + errors = fieldErrors.get("testBeanList[3].name"); + assertEquals(1, errors.size()); + errors = fieldErrors.get("testBeanList[4].name"); + assertEquals(1, errors.size()); + } + + public void testContextIsOverriddenByContextParamInValidationXML() throws Exception { + validate("visitorValidationAlias"); + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + assertEquals(3, fieldErrors.size()); + assertTrue(fieldErrors.containsKey("bean.count")); + assertTrue(fieldErrors.containsKey("bean.name")); + assertTrue(!fieldErrors.containsKey("bean.birth")); + + //the error from the action should be there too + assertTrue(fieldErrors.containsKey("context")); + } + + public void testContextIsPropagated() throws Exception { + validate("visitorValidation"); + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + assertEquals(3, fieldErrors.size()); + assertTrue(!fieldErrors.containsKey("bean.count")); + assertTrue(fieldErrors.containsKey("bean.name")); + assertTrue(fieldErrors.containsKey("bean.birth")); + + //the error from the action should be there too + assertTrue(fieldErrors.containsKey("context")); + } + + public void testVisitorChildValidation() throws Exception { + validate("visitorChildValidation"); + assertTrue(action.hasFieldErrors()); + + Map> fieldErrors = action.getFieldErrors(); + assertEquals(5, fieldErrors.size()); + assertTrue(!fieldErrors.containsKey("bean.count")); + assertTrue(fieldErrors.containsKey("bean.name")); + assertTrue(fieldErrors.containsKey("bean.birth")); + + assertTrue(fieldErrors.containsKey("bean.child.name")); + assertTrue(fieldErrors.containsKey("bean.child.birth")); + + //the error from the action should be there too + assertTrue(fieldErrors.containsKey("context")); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + ActionContext.setContext(null); + Locale.setDefault(origLocale); + } + + private void validate(String context) throws ValidationException { + ActionContext actionContext = ActionContext.getContext(); + actionContext.setName(context); + container.getInstance(ActionValidatorManager.class).validate(action, context); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorModelAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorModelAction.java new file mode 100644 index 000000000..9f16e3eb4 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorModelAction.java @@ -0,0 +1,35 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ModelDriven; + + +/** + * VisitorValidatorModelAction + * + * @author Jason Carreira + * Date: Mar 18, 2004 11:26:46 AM + */ +public class VisitorValidatorModelAction extends VisitorValidatorTestAction implements ModelDriven { + + /** + * @return the model to be pushed onto the ValueStack instead of the Action itself + */ + public Object getModel() { + return getBean(); + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorTestAction.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorTestAction.java new file mode 100644 index 000000000..2bebe4b63 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorTestAction.java @@ -0,0 +1,80 @@ +/* + * 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.validator; + +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.TestBean; + +import java.util.ArrayList; +import java.util.List; + + +/** + * VisitorValidatorTestAction + * + * @author Jason Carreira + * Created Aug 4, 2003 1:00:04 AM + */ +public class VisitorValidatorTestAction extends ActionSupport { + + private List testBeanList = new ArrayList(); + private String context; + private TestBean bean = new TestBean(); + private TestBean[] testBeanArray; + + + public VisitorValidatorTestAction() { + testBeanArray = new TestBean[5]; + + for (int i = 0; i < 5; i++) { + testBeanArray[i] = new TestBean(); + testBeanList.add(new TestBean()); + } + } + + + public void setBean(TestBean bean) { + this.bean = bean; + } + + public TestBean getBean() { + return bean; + } + + public void setContext(String context) { + this.context = context; + } + + public String getContext() { + return context; + } + + public void setTestBeanArray(TestBean[] testBeanArray) { + this.testBeanArray = testBeanArray; + } + + public TestBean[] getTestBeanArray() { + return testBeanArray; + } + + public void setTestBeanList(List testBeanList) { + this.testBeanList = testBeanList; + } + + public List getTestBeanList() { + return testBeanList; + } +} diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/validator/validators/ValidatorSupportTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/validators/ValidatorSupportTest.java new file mode 100644 index 000000000..6eb6ff217 --- /dev/null +++ b/xwork-core/src/test/java/com/opensymphony/xwork2/validator/validators/ValidatorSupportTest.java @@ -0,0 +1,58 @@ +/* + * 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.validator.validators; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.validator.ValidationException; + +/** + * @author tmjee + * @version $Date$ $Id$ + */ +public class ValidatorSupportTest extends XWorkTestCase { + + public void testConditionalParseExpression() throws Exception { + ValueStack oldStack = ActionContext.getContext().getValueStack(); + try { + OgnlValueStack stack = (OgnlValueStack) container.getInstance(ValueStackFactory.class).createValueStack(); + stack.getContext().put(ActionContext.CONTAINER, container); + stack.getContext().put("something", "somevalue"); + ActionContext.getContext().setValueStack(stack); + ValidatorSupport validator = new ValidatorSupport() { + public void validate(Object object) throws ValidationException { + } + }; + validator.setValueStack(ActionContext.getContext().getValueStack()); + + validator.setParse(true); + String result1 = validator.conditionalParse("${#something}").toString(); + + validator.setParse(false); + String result2 = validator.conditionalParse("${#something}").toString(); + + assertEquals(result1, "somevalue"); + assertEquals(result2, "${#something}"); + } + finally { + ActionContext.getContext().setValueStack(oldStack); + } + } + +} diff --git a/xwork-core/src/test/resources/PackagelessAction.properties b/xwork-core/src/test/resources/PackagelessAction.properties new file mode 100644 index 000000000..577c6d32c --- /dev/null +++ b/xwork-core/src/test/resources/PackagelessAction.properties @@ -0,0 +1 @@ +actionProperty = action property diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/ActionSupportTest$MyActionSupport_da.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/ActionSupportTest$MyActionSupport_da.properties new file mode 100644 index 000000000..b01a30fa2 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/ActionSupportTest$MyActionSupport_da.properties @@ -0,0 +1,8 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +hello=Hello World +hello.0=Hello World {0} +hello.1=Hello World. This is {0} speaking {1} diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/AnnotatedTestBean.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/AnnotatedTestBean.properties new file mode 100644 index 000000000..9e7b4a9cd --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/AnnotatedTestBean.properties @@ -0,0 +1,6 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +invalid.count=Count must be between ${min} and ${max}, current value is ${count}. diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/DefaultTextProviderTest_en_CA.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/DefaultTextProviderTest_en_CA.properties new file mode 100644 index 000000000..b01a30fa2 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/DefaultTextProviderTest_en_CA.properties @@ -0,0 +1,8 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +hello=Hello World +hello.0=Hello World {0} +hello.1=Hello World. This is {0} speaking {1} diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAction-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAction-validation.xml new file mode 100644 index 000000000..d24ed7eee --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAction-validation.xml @@ -0,0 +1,13 @@ + + + + + You must enter a value for count. + + + 1 + 10 + count must be between ${min} and ${max}, current value is ${count}. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAction.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAction.properties new file mode 100644 index 000000000..cb1555461 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAction.properties @@ -0,0 +1 @@ +invalid.fieldvalue.birth=Invalid date for birth. diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAnnotationAction.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAnnotationAction.properties new file mode 100644 index 000000000..d92a833df --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/ModelDrivenAnnotationAction.properties @@ -0,0 +1,6 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +invalid.fieldvalue.birth=Invalid date for birth. diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-some-alias-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-some-alias-validation.xml new file mode 100644 index 000000000..976fcf8dc --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-some-alias-validation.xml @@ -0,0 +1,13 @@ + + + + + You must enter a value for baz. + + + 2 + 4 + baz out of range. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-subproperty-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-subproperty-validation.xml new file mode 100644 index 000000000..dc8f2a469 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-subproperty-validation.xml @@ -0,0 +1,18 @@ + + + + + You must enter a name for the bean. + + + + + You must have a count for the bean. + + + 0 + 10 + bean.count out of range. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-validation.xml new file mode 100644 index 000000000..144f878eb --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-validation.xml @@ -0,0 +1,58 @@ + + + + + You must enter a value for bar. + + + 6 + 10 + bar must be between ${min} and ${max}, current value is ${bar}. + + + + + 0.1 + 10.1 + percentage must be between ${minExclusive} and ${maxExclusive}, current value is ${percentage}. + + + + + 12/22/2002 + 12/25/2002 + The date must be between 12-22-2002 and 12-25-2002. + + + + + 0 + 100 + Could not find foo.range! + + + + + 0 + Could not find baz.range! + + + + + 0 + 100 + Could not find foo.range! + + + + + 0 + 100 + Could not find foo.range! + + + + foo > bar + Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}. + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml new file mode 100644 index 000000000..976fcf8dc --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml @@ -0,0 +1,13 @@ + + + + + You must enter a value for baz. + + + 2 + 4 + baz out of range. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction.properties new file mode 100644 index 000000000..f99b6e736 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction.properties @@ -0,0 +1,3 @@ +foo.range=Foo Range Message +baz.range=${getText(fieldName)} must be greater than ${min} +baz=Baz Field diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction_de.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction_de.properties new file mode 100644 index 000000000..62f0bfcf6 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction_de.properties @@ -0,0 +1 @@ +foo.range=I don''t know German diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction_en.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAction_en.properties new file mode 100644 index 000000000..e69de29bb diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction.properties new file mode 100644 index 000000000..16819aaf3 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction.properties @@ -0,0 +1,8 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +foo.range=Foo Range Message +baz.range=${getText(fieldName)} must be greater than ${min} +baz=Baz Field diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction_de.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction_de.properties new file mode 100644 index 000000000..773a7fc92 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction_de.properties @@ -0,0 +1,6 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +foo.range=I don''t know German diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction_en.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction_en.properties new file mode 100644 index 000000000..7d0bcf9fe --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/SimpleAnnotationAction_en.properties @@ -0,0 +1,5 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-anotherContext-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-anotherContext-validation.xml new file mode 100644 index 000000000..821bdcd35 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-anotherContext-validation.xml @@ -0,0 +1,10 @@ + + + + + 1 + 100 + Count must be between ${min} and ${max}, current value is ${count}. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-badtest-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-badtest-validation.xml new file mode 100644 index 000000000..2eebc4f3c --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-badtest-validation.xml @@ -0,0 +1,8 @@ + + + + + You must enter a name. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-beanMessageBundle-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-beanMessageBundle-validation.xml new file mode 100644 index 000000000..9dc71ab50 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-beanMessageBundle-validation.xml @@ -0,0 +1,15 @@ + + + + + 1 + 100 + Invalid Count! + + + 20 + 80 + Smaller Invalid Count: ${count} + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-expressionValidation-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-expressionValidation-validation.xml new file mode 100644 index 000000000..050437644 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-expressionValidation-validation.xml @@ -0,0 +1,9 @@ + + + + + name.length() > 5 + Name must be greater than 5 characters, it is currently '${name}' + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-validation.xml new file mode 100644 index 000000000..42c3e1e27 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-validation.xml @@ -0,0 +1,8 @@ + + + + + You must enter a name. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-visitorChildValidation-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-visitorChildValidation-validation.xml new file mode 100644 index 000000000..94981ef3b --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-visitorChildValidation-validation.xml @@ -0,0 +1,14 @@ + + + + + 01/01/1970 + You must have been born after 1970. + + + + + child bean: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-visitorValidation-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-visitorValidation-validation.xml new file mode 100644 index 000000000..34673de4c --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean-visitorValidation-validation.xml @@ -0,0 +1,9 @@ + + + + + 01/01/1970 + You must have been born after 1970. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean.properties new file mode 100644 index 000000000..577c8e143 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestBean.properties @@ -0,0 +1 @@ +invalid.count=Count must be between ${min} and ${max}, current value is ${count}. diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TestChildBean-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestChildBean-validation.xml new file mode 100644 index 000000000..89e563a63 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TestChildBean-validation.xml @@ -0,0 +1,18 @@ + + + + + You must enter a name. + + + name == 'test' + Name is invalid + + + + + 01/01/1970 + You must have been born after 1970. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/TextProviderSupportTest_en.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/TextProviderSupportTest_en.properties new file mode 100644 index 000000000..a44802b6d --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/TextProviderSupportTest_en.properties @@ -0,0 +1,12 @@ +# +# Copyright (c) 2002-2006 by OpenSymphony +# All rights reserved. +# + +hello=Hello World +hello.0=Hello World {0} +hello.1=Hello World. This is {0} speaking {1} +#wrong (unescaped ', {, \): symbols1="=!@#$%^&*(){qwe}<>?:|}{[]\';/.,<>`~' +symbols1="=!@#$%^&*()'{'qwe}<>?:|}'{'[]\\'';/.,<>`~'' +#wrong: symbols1="=!@#$%^&*()<>?:|[]\';/.,<>`~' +symbols2="=!@#$%^&*()<>?:|[]\\'';/.,<>`~'' diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/ValidationOrderAction-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/ValidationOrderAction-validation.xml new file mode 100644 index 000000000..58fa1dca4 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/ValidationOrderAction-validation.xml @@ -0,0 +1,89 @@ + + + + + + + + username required + + + + + + password required + + + + + + confirm password required + + + + (confirmPassword.equals(password)) + + confirmed password must match password + + + + + + first name required + + + + + + last name required + + + + + + city is required + + + + + + province is required + + + + + + country is required + + + + + + postal code is required + + + + + + email is required + + + email is invalid + + + + + + website is required + + + + + + password hint is required + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder1/xwork-test-load-order.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder1/xwork-test-load-order.xml new file mode 100644 index 000000000..a7b5ddca3 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder1/xwork-test-load-order.xml @@ -0,0 +1,7 @@ + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder2/xwork-test-load-order.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder2/xwork-test-load-order.xml new file mode 100644 index 000000000..7ba44791b --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder2/xwork-test-load-order.xml @@ -0,0 +1,7 @@ + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder3/xwork-test-load-order.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder3/xwork-test-load-order.xml new file mode 100644 index 000000000..afcc8df35 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/loadorder3/xwork-test-load-order.xml @@ -0,0 +1,7 @@ + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork- test.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork- test.xml new file mode 100644 index 000000000..0417e5bf7 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork- test.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + fooDefault + + + + + + + + + + login + + + + + 17 + 23 + foo.jspa?fooID=${fooID}&something=bar + + something + + + + + + 18 + 24 + + + + + + + + + + + 18 + 24 + + + + + + + + expectedFooValue + + + + + + foo123 + expectedFooValue + + + + + 17 + 23 + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-after-package-2.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-after-package-2.xml new file mode 100644 index 000000000..61b64c4a1 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-after-package-2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-after-package.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-after-package.xml new file mode 100644 index 000000000..65c5a8674 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-after-package.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-before-package-2.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-before-package-2.xml new file mode 100644 index 000000000..f10469b9a --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-before-package-2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-before-package.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-before-package.xml new file mode 100644 index 000000000..3238385fb --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-before-package.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-parent.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-parent.xml new file mode 100644 index 000000000..cb2e6fb49 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-include-parent.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-action-invalid.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-action-invalid.xml new file mode 100644 index 000000000..69d2bca21 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-action-invalid.xml @@ -0,0 +1,23 @@ + + + + + + + + 13 + + + + 17 + + + + 17 + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-actions-packagedefaultclassref.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-actions-packagedefaultclassref.xml new file mode 100644 index 000000000..c8f97f572 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-actions-packagedefaultclassref.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + fooDefault + + + + + + + + + + + + login + + + + + + 17 + 23 + + + + \ No newline at end of file diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-actions.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-actions.xml new file mode 100644 index 000000000..0417e5bf7 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-actions.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + fooDefault + + + + + + + + + + login + + + + + 17 + 23 + foo.jspa?fooID=${fooID}&something=bar + + something + + + + + + 18 + 24 + + + + + + + + + + + 18 + 24 + + + + + + + + expectedFooValue + + + + + + foo123 + expectedFooValue + + + + + 17 + 23 + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml new file mode 100644 index 000000000..0e47f17c7 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml new file mode 100644 index 000000000..54580c23c --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml new file mode 100644 index 000000000..7f94a84b0 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml new file mode 100644 index 000000000..8f5556da9 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-exception-mappings.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-exception-mappings.xml new file mode 100644 index 000000000..01b0b9cd4 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-exception-mappings.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + login + + + + + + + + + + + + bar.vm + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-global-result-inheritence.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-global-result-inheritence.xml new file mode 100644 index 000000000..6dbc05b10 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-global-result-inheritence.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + p1 + + + p2 + + + + + + + + + + + + + a1 + + + a2 + + + + + + + + + c1 + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-defaultref.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-defaultref.xml new file mode 100644 index 000000000..91c82cda2 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-defaultref.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-inheritance.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-inheritance.xml new file mode 100644 index 000000000..75c8b6fbe --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-inheritance.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-param-overriding.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-param-overriding.xml new file mode 100644 index 000000000..ca6986789 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-param-overriding.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + i1p1 + i1p2 + i2p1 + + test1 + + + + + i3p1 + i3p2 + i2p2 + + test2 + + + + \ No newline at end of file diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-params.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-params.xml new file mode 100644 index 000000000..c1779b994 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-params.xml @@ -0,0 +1,37 @@ + + + + + + + + + fooDefault + + + + + + + + + + + + + expectedFooValue + + + + + + foo123 + expectedFooValue2 + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-stack-param-overriding.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-stack-param-overriding.xml new file mode 100644 index 000000000..3beb3679e --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptor-stack-param-overriding.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + i1p1 + i1p2 + i2p1 + + test1 + + + + + i3p1 + i3p2 + i2p2 + + test2 + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptors-basic.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptors-basic.xml new file mode 100644 index 000000000..845111cbf --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptors-basic.xml @@ -0,0 +1,27 @@ + + + + + + + + + + expectedFoo + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptors-spring.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptors-spring.xml new file mode 100644 index 000000000..520bd626b --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-interceptors-spring.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-invalid-file.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-invalid-file.xml new file mode 100644 index 000000000..88310756e --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-invalid-file.xml @@ -0,0 +1,9 @@ + + 17 + 23 + + Bar + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-multilevel.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-multilevel.xml new file mode 100644 index 000000000..df3acaa6d --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-multilevel.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml new file mode 100644 index 000000000..0c8055632 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-inheritance.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-inheritance.xml new file mode 100644 index 000000000..8915a1fe7 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-inheritance.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-types.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-types.xml new file mode 100644 index 000000000..e6ee58239 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-result-types.xml @@ -0,0 +1,54 @@ + + + + + + + + + + value1 + value2 + value3 + + + valueA + valueB + + + + + + + + + value1 + value2 + value3 + + + valueA + valueB + + + + + + newValue1 + newValue3 + value10 + value11 + + + + + + newValueB + valueZ + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-results.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-results.xml new file mode 100644 index 000000000..3291d174a --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-results.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + login + + + + + + + bar.vm + + foo.vm + + foo.vm + bar + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml new file mode 100644 index 000000000..c92d057e0 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-1.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-2.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-2.xml new file mode 100644 index 000000000..f77c3b403 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-2.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-include.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-include.xml new file mode 100644 index 000000000..cb097a251 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-wildcard-include.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack-empty.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack-empty.xml new file mode 100644 index 000000000..55fbec1e6 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack-empty.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack.xml new file mode 100644 index 000000000..02b7fd7b2 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-unknownhandler-stack.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/conversion/impl/test-xwork-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/conversion/impl/test-xwork-conversion.properties new file mode 100644 index 000000000..42fde481b --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/conversion/impl/test-xwork-conversion.properties @@ -0,0 +1 @@ +com.opensymphony.xwork2.util.Bar=com.opensymphony.xwork2.conversion.impl.FooBarConverter \ No newline at end of file diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/somefile.txt b/xwork-core/src/test/resources/com/opensymphony/xwork2/somefile.txt new file mode 100644 index 000000000..2b9e2512a --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/somefile.txt @@ -0,0 +1,9 @@ +this +is +a +file +of +great +import +or +something diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml new file mode 100644 index 000000000..4cc228437 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml @@ -0,0 +1,42 @@ + + + + + + + + injected + + + + + + false + true + + + execute-interceptor + + simple-action + + + + + + + + true + + + execute-interceptor + + + auto-proxied-action + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml new file mode 100644 index 000000000..5bcb44652 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/autowireContext.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/autowireContext.xml new file mode 100644 index 000000000..059bb4a7f --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/autowireContext.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/resolverApplicationContext.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/resolverApplicationContext.xml new file mode 100644 index 000000000..6f0908f92 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/resolverApplicationContext.xml @@ -0,0 +1,11 @@ + + + + + + Little Foo + + + 16 + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/xwork-autowire.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/xwork-autowire.xml new file mode 100644 index 000000000..ad7e90c2b --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/xwork-autowire.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + @org.springframework.beans.factory.config.AutowireCapableBeanFactory@AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE + + + + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/test.properties new file mode 100644 index 000000000..7edc23d67 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test.properties @@ -0,0 +1 @@ +xwork.error.action.execution=Testing resource bundle override diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-conversion.properties new file mode 100644 index 000000000..cd84249e0 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-conversion.properties @@ -0,0 +1 @@ +barObj=com.opensymphony.xwork2.conversion.impl.FooBarConverter diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-validation.xml new file mode 100644 index 000000000..4c73c0825 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-validation.xml @@ -0,0 +1,8 @@ + + + + + You must enter a value for data. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml new file mode 100644 index 000000000..154ee3097 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml @@ -0,0 +1,8 @@ + + + + + You must enter a value for data. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware.properties new file mode 100644 index 000000000..64800c5ff --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware.properties @@ -0,0 +1,2 @@ +test.foo = Foo! +test.bar = Bar! diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware2-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware2-validation.xml new file mode 100644 index 000000000..3e017591c --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/DataAware2-validation.xml @@ -0,0 +1,8 @@ + + + + + You must enter a value for data. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/Equidae-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/Equidae-validation.xml new file mode 100644 index 000000000..8c9d00ec5 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/Equidae-validation.xml @@ -0,0 +1,52 @@ + + + + + false + noTrim + + + true + trim + + + + + + false + 5 + noTrim-min5 + + + false + 5 + 10 + noTrim-min5-max10 + + + false + 10 + noTrim-max10 + + + + + + true + 5 + trim-min5 + + + true + 5 + 10 + trim-min5-max10 + + + true + 10 + trim-max10 + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/SimpleAction2-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/SimpleAction2-validation.xml new file mode 100644 index 000000000..36edcec4a --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/SimpleAction2-validation.xml @@ -0,0 +1,13 @@ + + + + + You must enter a value for count. + + + 0 + 5 + count must be between ${min} and ${max}, current value is ${count}. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/SimpleAction2-validationAlias-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/SimpleAction2-validationAlias-validation.xml new file mode 100644 index 000000000..976fcf8dc --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/SimpleAction2-validationAlias-validation.xml @@ -0,0 +1,13 @@ + + + + + You must enter a value for baz. + + + 2 + 4 + baz out of range. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/TestBean2-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/TestBean2-conversion.properties new file mode 100644 index 000000000..6b1e4df60 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/TestBean2-conversion.properties @@ -0,0 +1 @@ +cat = com.opensymphony.xwork2.conversion.impl.FooBarConverter diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/User-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/User-conversion.properties new file mode 100644 index 000000000..171c6106b --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/User-conversion.properties @@ -0,0 +1,2 @@ +Collection_list = java.lang.String +Collection_map = java.lang.String diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/User-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/User-validation.xml new file mode 100644 index 000000000..e67ea7db6 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/User-validation.xml @@ -0,0 +1,38 @@ + + + + + You must enter a value for name. + + + + + + Not a valid e-mail. + + + email.endsWith('mycompany.com') + Email not from the right company. + + + + + + Not a valid e-mail2. + + + email.endsWith('mycompany.com') + Email2 not from the right company. + + + + + email.startsWith('mark') + Email does not start with mark + + + email2.startsWith('mark') + Email2 does not start with mark + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/UserMarker-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/UserMarker-validation.xml new file mode 100644 index 000000000..6545ca16f --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/UserMarker-validation.xml @@ -0,0 +1,17 @@ + + + + + You must enter a value for email. + + + + + You must enter a value for email2. + + + + email.equals(email2) + Email not the same as email2 + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/test/package.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/package.properties new file mode 100644 index 000000000..269e24957 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/test/package.properties @@ -0,0 +1 @@ +package.properties=It works! diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties new file mode 100644 index 000000000..c60d3e9cf --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties @@ -0,0 +1,2 @@ +title=Title: +invalid.fieldvalue.title=Title is invalid! diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Cat-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Cat-conversion.properties new file mode 100644 index 000000000..7a9efb600 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Cat-conversion.properties @@ -0,0 +1,2 @@ +Collection_kittens = com.opensymphony.xwork2.util.Cat +foo.number = com.opensymphony.xwork2.conversion.impl.FooNumberConverter diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/FindMe.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/FindMe.properties new file mode 100644 index 000000000..8ee6614fa --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/FindMe.properties @@ -0,0 +1,2 @@ +bean.name=Haha you cant FindMe! +bean2.name=Okay! You found Me! diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Foo-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Foo-conversion.properties new file mode 100644 index 000000000..09b79ace7 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Foo-conversion.properties @@ -0,0 +1,11 @@ +bar=com.opensymphony.xwork2.conversion.impl.FooBarConverter +Element_cats=com.opensymphony.xwork2.util.Cat +Element_moreCats=com.opensymphony.xwork2.util.Cat +Element_catMap=com.opensymphony.xwork2.util.Cat +Key_anotherCatMap=java.lang.Long +Element_anotherCatMap=com.opensymphony.xwork2.util.Cat +KeyProperty_barCollection=id +Element_barCollection=com.opensymphony.xwork2.util.Bar +KeyProperty_barList=id +Element_barList=com.opensymphony.xwork2.util.Bar + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/ListHolder-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/ListHolder-conversion.properties new file mode 100644 index 000000000..e7d68394f --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/ListHolder-conversion.properties @@ -0,0 +1,3 @@ +Element_longs = java.lang.Long +Element_strings = java.lang.String +Element_dates = java.util.Date diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties new file mode 100644 index 000000000..5bde086ea --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties @@ -0,0 +1,3 @@ +test.format.date={0,date,short} +xw377=xw377 +username=Santa diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/MyBeanAction-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/MyBeanAction-conversion.properties new file mode 100644 index 000000000..b8d68d6ac --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/MyBeanAction-conversion.properties @@ -0,0 +1,8 @@ +KeyProperty_beanList=id +Element_beanList=com.opensymphony.xwork2.util.MyBean +CreateIfNull_beanList=true + +Key_beanMap=java.lang.Long +KeyProperty_beanMap=id +Element_beanMap=com.opensymphony.xwork2.util.MyBean + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Tiger-conversion.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Tiger-conversion.properties new file mode 100644 index 000000000..ebe7be17a --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/Tiger-conversion.properties @@ -0,0 +1 @@ +Collection_dogs = com.opensymphony.xwork2.util.Dog diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/XW404_de.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/XW404_de.properties new file mode 100644 index 000000000..b1f6327b2 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/XW404_de.properties @@ -0,0 +1,3 @@ +# Do not create a default bundle, XW404 tests a condition where there are no default bundle + +hello=Hallo diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/XW404_fr.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/XW404_fr.properties new file mode 100644 index 000000000..a1766b32e --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/XW404_fr.properties @@ -0,0 +1,3 @@ +# Do not create a default bundle, XW404 tests a condition where there are no default bundle + +hello=Bonjour diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/util/location/xml-with-location.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/location/xml-with-location.xml new file mode 100644 index 000000000..ea4fb6998 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/util/location/xml-with-location.xml @@ -0,0 +1,8 @@ + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/CompositeTextProviderTestResourceBundle1.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/CompositeTextProviderTestResourceBundle1.properties new file mode 100644 index 000000000..f365a4ace --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/CompositeTextProviderTestResourceBundle1.properties @@ -0,0 +1,10 @@ +# common in both CompositeTextProviderTestResourceBundle1.properties +# and CompositeTextProviderTestResourceBunlde2.properties +name=1 name +age=1 age +goodnight=1 good night {0} +goodmorning=1 good morning {0} and {1} + +# specific to CompositeTextProviderTestResourceBundle1.properties +car=This is a car +bike=This is a bike diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/CompositeTextProviderTestResourceBundle2.properties b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/CompositeTextProviderTestResourceBundle2.properties new file mode 100644 index 000000000..d030485d2 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/CompositeTextProviderTestResourceBundle2.properties @@ -0,0 +1,11 @@ +# common in both CompositeTextProviderTestResourceBundle1.properties +# and CompositeTextProviderTestResourceBunlde2.properties +name=2 name +age=2 age +goodnight=2 good night {0} +goodmorning=2 good morning {0} and {1} + + +# specific to CompositeTextProviderTestResourceBundle2.properties +cat=This is a cat +dog=This is a dog diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorModelAction-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorModelAction-validation.xml new file mode 100644 index 000000000..909a547d8 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorModelAction-validation.xml @@ -0,0 +1,9 @@ + + + + + false + model: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-beanMessageBundle-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-beanMessageBundle-validation.xml new file mode 100644 index 000000000..80a57bbbd --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-beanMessageBundle-validation.xml @@ -0,0 +1,8 @@ + + + + + bean: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validateArray-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validateArray-validation.xml new file mode 100644 index 000000000..70f1d6c9e --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validateArray-validation.xml @@ -0,0 +1,8 @@ + + + + + testBeanArray: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validateList-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validateList-validation.xml new file mode 100644 index 000000000..9862d7a9e --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validateList-validation.xml @@ -0,0 +1,8 @@ + + + + + testBeanList: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validation.xml new file mode 100644 index 000000000..bd075008c --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-validation.xml @@ -0,0 +1,8 @@ + + + + + You must enter a context. + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorChildValidation-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorChildValidation-validation.xml new file mode 100644 index 000000000..80a57bbbd --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorChildValidation-validation.xml @@ -0,0 +1,8 @@ + + + + + bean: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorValidation-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorValidation-validation.xml new file mode 100644 index 000000000..80a57bbbd --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorValidation-validation.xml @@ -0,0 +1,8 @@ + + + + + bean: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorValidationAlias-validation.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorValidationAlias-validation.xml new file mode 100644 index 000000000..f53227a00 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/VisitorValidatorTestAction-visitorValidationAlias-validation.xml @@ -0,0 +1,9 @@ + + + + + anotherContext + bean: + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test.xml new file mode 100644 index 000000000..2994255d6 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test.xml @@ -0,0 +1,33 @@ + + + + + You must enter a value for foo. + + + + + You must enter a value for bar. + + + 6 + 10 + bar must be between ${min} and ${max}, current value is ${bar}. + + + + + + + bar must must match the given expression. + + + + email.equals(email2) + Email not the same as email2 + + + email.startsWith('mark') + Email does not start with mark + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test2.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test2.xml new file mode 100644 index 000000000..5336f14f1 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test2.xml @@ -0,0 +1,17 @@ + + + + + + a field error message + + + + + an expression error message + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test3.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test3.xml new file mode 100644 index 000000000..7ffe92ddd --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test3.xml @@ -0,0 +1,11 @@ + + + + email.equals(email2) + Email not the same as email2 + + + email.startsWith('mark') + Email does not start with mark + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test4.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test4.xml new file mode 100644 index 000000000..eb2682e36 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test4.xml @@ -0,0 +1,12 @@ + + + + + email.equals(email2) + Email not the same as email2 + + + email.startsWith('mark') + Email does not start with mark + +/validators> diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test5.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test5.xml new file mode 100644 index 000000000..6125506eb --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test5.xml @@ -0,0 +1,4 @@ + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test6.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test6.xml new file mode 100644 index 000000000..2f304d19c --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validator-parser-test6.xml @@ -0,0 +1,27 @@ + + + + + + default message 1 + + + + + true + anotherValue + ddddd + 'tmjee' + 'phil' + 'jimmy' + 'hopkins' + The Default Message + Some Nonesense Value + 'rainer' + + + + + diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validators-fail.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validators-fail.xml new file mode 100644 index 000000000..c3d73ea55 --- /dev/null +++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/validator/validators-fail.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/includeTest.xml b/xwork-core/src/test/resources/includeTest.xml new file mode 100644 index 000000000..beae6bfe6 --- /dev/null +++ b/xwork-core/src/test/resources/includeTest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/xwork-core/src/test/resources/log4j.properties b/xwork-core/src/test/resources/log4j.properties new file mode 100644 index 000000000..89cf0df0d --- /dev/null +++ b/xwork-core/src/test/resources/log4j.properties @@ -0,0 +1,17 @@ +log4j.rootLogger = WARN, stdout + +log4j.appender.stdout = org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Threshold = WARN +log4j.appender.stdout.Target = System.out +log4j.appender.stdout.layout = org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n + +# set to info to let the code be executed when doing unit test for this interceptor +log4j.category.com.opensymphony.xwork2.interceptor.LoggingInterceptor=INFO +log4j.category.com.opensymphony.xwork2.interceptor.TimerInterceptor=INFO + +# set to debug to let the code be executed when doing unit test for this interceptor +log4j.category.com.opensymphony.xwork2.interceptor.ParametersInterceptor=DEBUG + +# set to debug for testing timer interceptor with custom log category +log4j.category.com.mycompany.myapp.actiontiming=DEBUG diff --git a/xwork-core/src/test/resources/my-validators.xml b/xwork-core/src/test/resources/my-validators.xml new file mode 100644 index 000000000..148392eff --- /dev/null +++ b/xwork-core/src/test/resources/my-validators.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/xwork-core/src/test/resources/myOther-validators.xml b/xwork-core/src/test/resources/myOther-validators.xml new file mode 100644 index 000000000..aeeb88164 --- /dev/null +++ b/xwork-core/src/test/resources/myOther-validators.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/xwork-core/src/test/resources/validators.xml b/xwork-core/src/test/resources/validators.xml new file mode 100644 index 000000000..4b6a7fde3 --- /dev/null +++ b/xwork-core/src/test/resources/validators.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/xwork - jar.jar b/xwork-core/src/test/resources/xwork - jar.jar new file mode 100644 index 000000000..13307aff5 Binary files /dev/null and b/xwork-core/src/test/resources/xwork - jar.jar differ diff --git a/xwork-core/src/test/resources/xwork - zip.zip b/xwork-core/src/test/resources/xwork - zip.zip new file mode 100644 index 000000000..589206a59 Binary files /dev/null and b/xwork-core/src/test/resources/xwork - zip.zip differ diff --git a/xwork-core/src/test/resources/xwork-1.0.dtd b/xwork-core/src/test/resources/xwork-1.0.dtd new file mode 100644 index 000000000..d04fb7b2f --- /dev/null +++ b/xwork-core/src/test/resources/xwork-1.0.dtd @@ -0,0 +1 @@ +Duplicate file test \ No newline at end of file diff --git a/xwork-core/src/test/resources/xwork-jar.jar b/xwork-core/src/test/resources/xwork-jar.jar new file mode 100644 index 000000000..80e93fc1d Binary files /dev/null and b/xwork-core/src/test/resources/xwork-jar.jar differ diff --git a/xwork-core/src/test/resources/xwork-proxyinvoke.xml b/xwork-core/src/test/resources/xwork-proxyinvoke.xml new file mode 100644 index 000000000..3feec242f --- /dev/null +++ b/xwork-core/src/test/resources/xwork-proxyinvoke.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + expectedFoo + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/xwork-sample.xml b/xwork-core/src/test/resources/xwork-sample.xml new file mode 100644 index 000000000..6eb681589 --- /dev/null +++ b/xwork-core/src/test/resources/xwork-sample.xml @@ -0,0 +1,238 @@ + + + + + + + + + + login + + + + + 17 + 23 + + Bar + + + + + + + 17 + 23 + + + + + + + 17 + 23 + + + + + + + + + + + + + {1} + {2} + + + + + + #{ "aliasSource" : "aliasDest", "bar":"baz" } + + + + + + + + + + 17 + 23 + + + + + + + + + + + + expectedFoo + + + + + + + foo123 + foo123 + + + + + + + + + + + + + InfiniteRecursionChain + + + + + + + + + + + + + + + + + + + + + + + + expectedFoo + + + + + + + foo123 + foo123 + + + + + + + + + + + + + + 123 + + bar + + + + + + 456 + + foo + + + + + + + + + + + + 456 + + foo + + + + + + + + + + + + + + + + foo + + + + + + + + + + + edit.vm + edit.vm + edit.vm + + + edit.vm + list + edit.vm + list.action + + + list + + + list + + + + + + + + + + + + somethingelse.vm + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/xwork-test-beans.xml b/xwork-core/src/test/resources/xwork-test-beans.xml new file mode 100644 index 000000000..420810641 --- /dev/null +++ b/xwork-core/src/test/resources/xwork-test-beans.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/xwork-core/src/test/resources/xwork-test-default.xml b/xwork-core/src/test/resources/xwork-test-default.xml new file mode 100644 index 000000000..048a41be3 --- /dev/null +++ b/xwork-core/src/test/resources/xwork-test-default.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + expectedFoo + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/xwork-test-validation.xml b/xwork-core/src/test/resources/xwork-test-validation.xml new file mode 100644 index 000000000..b7bf800f4 --- /dev/null +++ b/xwork-core/src/test/resources/xwork-test-validation.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + true + + + + expectedFoo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwork-core/src/test/resources/xwork-zip.zip b/xwork-core/src/test/resources/xwork-zip.zip new file mode 100644 index 000000000..81322dddd Binary files /dev/null and b/xwork-core/src/test/resources/xwork-zip.zip differ diff --git a/xwork-core/test-output/Command line suite/Command line test.html b/xwork-core/test-output/Command line suite/Command line test.html new file mode 100644 index 000000000..869fc4c0b --- /dev/null +++ b/xwork-core/test-output/Command line suite/Command line test.html @@ -0,0 +1,79 @@ + + +TestNG: Command line test + + + + + + + + +

Command line test

+ + + + + + + + + + + +
Tests passed/Failed/Skipped:1/0/0
Started on:Thu Dec 03 21:49:46 CET 2009
Total time:0 seconds
Included groups:
Excluded groups:

+(Hoover the method name to see the test class name)

+ + + + + + + + + + + +
PASSED TESTS
Test methodTime (seconds)Exception
testRun0

+ + \ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/Command line test.properties b/xwork-core/test-output/Command line suite/Command line test.properties new file mode 100644 index 000000000..d5d28e7cb --- /dev/null +++ b/xwork-core/test-output/Command line suite/Command line test.properties @@ -0,0 +1 @@ +[SuiteResult Command line test] \ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/Command line test.xml b/xwork-core/test-output/Command line suite/Command line test.xml new file mode 100644 index 000000000..9fa65ddf0 --- /dev/null +++ b/xwork-core/test-output/Command line suite/Command line test.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/classes.html b/xwork-core/test-output/Command line suite/classes.html new file mode 100644 index 000000000..1016ab140 --- /dev/null +++ b/xwork-core/test-output/Command line suite/classes.html @@ -0,0 +1,12 @@ +

Test classes


com.opensymphony.xwork2.TestNGXWorkTestCaseTest$RunTest

+
        Test methods +
                testRun() +
+
        @BeforeClass +
+
        @BeforeMethod +
+
        @AfterMethod +
+
        @AfterClass +
diff --git a/xwork-core/test-output/Command line suite/groups.html b/xwork-core/test-output/Command line suite/groups.html new file mode 100644 index 000000000..199cb3f11 --- /dev/null +++ b/xwork-core/test-output/Command line suite/groups.html @@ -0,0 +1 @@ +

Groups used for this test run

\ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/index.html b/xwork-core/test-output/Command line suite/index.html new file mode 100644 index 000000000..3577bd28a --- /dev/null +++ b/xwork-core/test-output/Command line suite/index.html @@ -0,0 +1,6 @@ +Results for Command line suite + + + + + diff --git a/xwork-core/test-output/Command line suite/main.html b/xwork-core/test-output/Command line suite/main.html new file mode 100644 index 000000000..0ee4b5b49 --- /dev/null +++ b/xwork-core/test-output/Command line suite/main.html @@ -0,0 +1,2 @@ +Results for Command line suite +Select a result on the left-hand pane. diff --git a/xwork-core/test-output/Command line suite/methods-alphabetical.html b/xwork-core/test-output/Command line suite/methods-alphabetical.html new file mode 100644 index 000000000..f8d247bb3 --- /dev/null +++ b/xwork-core/test-output/Command line suite/methods-alphabetical.html @@ -0,0 +1,10 @@ +

Methods run, sorted chronologically

>> means before, << means after


Command line suite

(Hoover the method name to see the test class name)

+ + + + + + + + +
TimeDelta (ms)Suite
configuration
Test
configuration
Class
configuration
Groups
configuration
Method
configuration
Test
method
ThreadInstances
09/12/03 21:49:46 0  >>setUp     465264835
09/12/03 21:49:46 69  <<tearDown     465264835
09/12/03 21:49:46 41      testRun465264835
diff --git a/xwork-core/test-output/Command line suite/methods-not-run.html b/xwork-core/test-output/Command line suite/methods-not-run.html new file mode 100644 index 000000000..c26446cc9 --- /dev/null +++ b/xwork-core/test-output/Command line suite/methods-not-run.html @@ -0,0 +1,2 @@ +

Disabled methods

+
\ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/methods.html b/xwork-core/test-output/Command line suite/methods.html new file mode 100644 index 000000000..852b49df5 --- /dev/null +++ b/xwork-core/test-output/Command line suite/methods.html @@ -0,0 +1,10 @@ +

Methods run, sorted chronologically

>> means before, << means after


Command line suite

(Hoover the method name to see the test class name)

+ + + + + + + + +
TimeDelta (ms)Suite
configuration
Test
configuration
Class
configuration
Groups
configuration
Method
configuration
Test
method
ThreadInstances
09/12/03 21:49:46 0  >>setUp     465264835
09/12/03 21:49:46 41      testRun465264835
09/12/03 21:49:46 69  <<tearDown     465264835
diff --git a/xwork-core/test-output/Command line suite/reporter-output.html b/xwork-core/test-output/Command line suite/reporter-output.html new file mode 100644 index 000000000..063bc2e96 --- /dev/null +++ b/xwork-core/test-output/Command line suite/reporter-output.html @@ -0,0 +1 @@ +

Reporter output

\ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/testng-failed.xml b/xwork-core/test-output/Command line suite/testng-failed.xml new file mode 100644 index 000000000..e9402a924 --- /dev/null +++ b/xwork-core/test-output/Command line suite/testng-failed.xml @@ -0,0 +1,3 @@ + + + diff --git a/xwork-core/test-output/Command line suite/testng.xml.html b/xwork-core/test-output/Command line suite/testng.xml.html new file mode 100644 index 000000000..57bd296a8 --- /dev/null +++ b/xwork-core/test-output/Command line suite/testng.xml.html @@ -0,0 +1 @@ +testng.xml for Command line suite<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite thread-count="5" verbose="1" name="Command line suite" parallel="false" annotations="JDK5">
  <test name="Command line test" junit="false">
    <classes>
      <class name="com.opensymphony.xwork2.TestNGXWorkTestCaseTest$RunTest"/>
    </classes>
  </test>
</suite>
\ No newline at end of file diff --git a/xwork-core/test-output/Command line suite/toc.html b/xwork-core/test-output/Command line suite/toc.html new file mode 100644 index 000000000..1c5f2d349 --- /dev/null +++ b/xwork-core/test-output/Command line suite/toc.html @@ -0,0 +1,30 @@ + + +Results for Command line suite + + + + +

Results for
Command line suite

+ + + + + + + + + + +
1 test1 class1 method:
+  chronological
+  alphabetical
+  not run
0 groupreporter outputtestng.xml
+ +

+

+
Command line test (1/0/0) + Results +
+
+ \ No newline at end of file diff --git a/xwork-core/test-output/index.html b/xwork-core/test-output/index.html new file mode 100644 index 000000000..d30967d25 --- /dev/null +++ b/xwork-core/test-output/index.html @@ -0,0 +1,8 @@ + +Test results + + +

Test results

+ + +
SuitePassedFailedSkippedtestng.xml
Command line suite100Link
diff --git a/xwork-core/test-output/testng-failed.xml b/xwork-core/test-output/testng-failed.xml new file mode 100644 index 000000000..e9402a924 --- /dev/null +++ b/xwork-core/test-output/testng-failed.xml @@ -0,0 +1,3 @@ + + + diff --git a/xwork-core/test-output/testng.css b/xwork-core/test-output/testng.css new file mode 100644 index 000000000..5124ba863 --- /dev/null +++ b/xwork-core/test-output/testng.css @@ -0,0 +1,9 @@ +.invocation-failed, .test-failed { background-color: #DD0000; } +.invocation-percent, .test-percent { background-color: #006600; } +.invocation-passed, .test-passed { background-color: #00AA00; } +.invocation-skipped, .test-skipped { background-color: #CCCC00; } + +.main-page { + font-size: x-large; +} +