Move XWork core under Struts 2 to simplify relese proces

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@911280 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Lukasz Lenart
2010-02-18 08:22:46 +00:00
parent 053dfd2b05
commit 0c543aef31
686 changed files with 72963 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.opensymphony</groupId>
<artifactId>xwork-core</artifactId>
<packaging>jar</packaging>
<name>XWork: Core</name>
<parent>
<groupId>com.opensymphony</groupId>
<artifactId>xwork-parent</artifactId>
<version>2.1.7-SNAPSHOT</version>
</parent>
<scm>
<connection>scm:svn:http://svn.opensymphony.com/svn/xwork/trunk/core</connection>
<developerConnection>
scm:svn:https://svn.opensymphony.com/svn/xwork/trunk/core
</developerConnection>
<url>https://svn.opensymphony.com/svn/xwork/trunk/core</url>
</scm>
<licenses>
<license>
<name>The OpenSymphony Software License 1.1</name>
<url>../src/resources/LICENSE.txt</url>
<comments>
This license is derived and fully compatible with the Apache Software
License - see http://www.apache.org/LICENSE.txt
</comments>
</license>
</licenses>
<profiles>
<profile>
<!--
Run the translator for Java 1.4 compatiblity
OS/X Sample:
$ cd xwork/
$ mvn clean install -Pj4 -Djava14.jar=$JAVA_HOME/../Classes/classes.jar
-->
<id>j4</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>retrotranslator-maven-plugin</artifactId>
<version>1.0-alpha-4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>translate-project</goal>
</goals>
<configuration>
<verify>false</verify>
<failonwarning>true</failonwarning>
<lazy>true</lazy>
<advanced>true</advanced>
<verbose>false</verbose>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!--
<dependencies>
<dependency>
<groupId>sun.jdk</groupId>
<artifactId>rt</artifactId>
<version>1.4.0</version>
<scope>system</scope>
<systemPath>${java14.jar}</systemPath>
</dependency>
<dependency>
<groupId>net.sf.retrotranslator</groupId>
<artifactId>retrotranslator-runtime</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>net.sf.retrotranslator</groupId>
<artifactId>retrotranslator-transformer</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
-->
</profile>
</profiles>
<build>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
<testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${basedir}/src/test/resources</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<additionalClasspathElements>
<element>${project.build.testOutputDirectory}/xwork-jar.jar</element>
<element>${project.build.testOutputDirectory}/xwork-zip.zip</element>
<element>${project.build.testOutputDirectory}/xwork - jar.jar</element>
<element>${project.build.testOutputDirectory}/xwork - zip.zip</element>
</additionalClasspathElements>
<includes>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/XWorkTestCase.java</exclude>
<exclude>**/TestBean.java</exclude>
<exclude>**/TestBean2.java</exclude>
<exclude>**/TestInterceptor.java</exclude>
<exclude>**/AnnotatedTestBean.java</exclude>
<exclude>**/ContainerImplTest.java</exclude>
<exclude>**/URLUtilTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactId>xwork-core</shadedArtifactId>
<artifactSet>
<excludes>
<exclude>junit:junit</exclude>
<exclude>commons-logging:commons-logging</exclude>
<exclude>opensymphony:ognl</exclude>
<exclude>ognl:ognl</exclude>
<exclude>jboss:javassist</exclude>
<exclude>org.springframework:spring-core</exclude>
<exclude>org.springframework:spring-aop</exclude>
<exclude>org.springframework:spring-aspects</exclude>
<exclude>org.springframework:spring-beans</exclude>
<exclude>org.springframework:spring-context</exclude>
<exclude>org.springframework:spring-context-support</exclude>
<exclude>org.springframework:spring-web</exclude>
<exclude>org.springframework:spring-test</exclude>
<exclude>mockobjects:mockobjects-core</exclude>
<exclude>org.easymock:easymock</exclude>
<exclude>aopalliance:aopalliance</exclude>
<exclude>aspectwerkz:aspectwerkz-core</exclude>
<exclude>org.aspectj:aspectjrt</exclude>
<exclude>org.aspectj:aspectjweaver</exclude>
<exclude>cglib:cglib</exclude>
<exclude>cglib:cglib-nodep</exclude>
<exclude>asm:asm-util</exclude>
<exclude>org.testng:testng:jdk15</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>commons-lang:commons-lang</artifact>
<includes>
<include>org/apache/commons/lang/StringUtils.class</include>
<include>org/apache/commons/lang/math/NumberUtils.class</include>
<include>org/apache/commons/lang/ObjectUtils*.class</include>
<include>org/apache/commons/lang/StringEscapeUtils.class</include>
<include>org/apache/commons/lang/exception/NestableRuntimeException.class</include>
<include>org/apache/commons/lang/exception/Nestable.class</include>
<include>org/apache/commons/lang/Entities*class</include>
<include>org/apache/commons/lang/UnhandledException.class</include>
<include>org/apache/commons/lang/IntHashMap*class</include>
</includes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>org.objectweb.asm</pattern>
<shadedPattern>org.objectweb.asm.xwork</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons.lang</pattern>
<shadedPattern>org.apache.commons.lang.xwork</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Export-Package>org.apache.commons.lang.xwork.*,com.opensymphony.xwork2.*</Export-Package>
</instructions>
</configuration>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<defaultGoal>install</defaultGoal>
</build>
</project>
@@ -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 <b>may</b> implement this interface, which exposes the <code>execute()</code> method.
* <p/>
* However, as of XWork 1.1, this is <b>not</b> 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.
* <p/>
* 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.
* <b>Note:</b> Application level exceptions should be handled by returning
* an error value, such as <code>Action.ERROR</code>.
*/
public String execute() throws Exception;
}
@@ -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.*;
/**
* <!-- START SNIPPET: description -->
*
* This result invokes an entire other action, complete with it's own interceptor stack and result.
*
* <!-- END SNIPPET: description -->
*
* <b>This result type takes the following parameters:</b>
*
* <!-- START SNIPPET: params -->
*
* <ul>
*
* <li><b>actionName (default)</b> - the name of the action that will be chained to</li>
*
* <li><b>namespace</b> - used to determine which namespace the Action is in that we're chaining. If namespace is null,
* this defaults to the current namespace</li>
*
* <li><b>method</b> - used to specify another method on target action to be invoked.
* If null, this defaults to execute method</li>
*
* <li><b>skipActions</b> - (optional) the list of comma separated action names for the
* actions that could be chained to</li>
*
* </ul>
*
* <!-- END SNIPPET: params -->
*
* <b>Example:</b>
*
* <pre><!-- START SNIPPET: example -->
* &lt;package name="public" extends="struts-default"&gt;
* &lt;!-- Chain creatAccount to login, using the default parameter --&gt;
* &lt;action name="createAccount" class="..."&gt;
* &lt;result type="chain"&gt;login&lt;/result&gt;
* &lt;/action&gt;
*
* &lt;action name="login" class="..."&gt;
* &lt;!-- Chain to another namespace --&gt;
* &lt;result type="chain"&gt;
* &lt;param name="actionName"&gt;dashboard&lt;/param&gt;
* &lt;param name="namespace"&gt;/secure&lt;/param&gt;
* &lt;/result&gt;
* &lt;/action&gt;
* &lt;/package&gt;
*
* &lt;package name="secure" extends="struts-default" namespace="/secure"&gt;
* &lt;action name="dashboard" class="..."&gt;
* &lt;result&gt;dashboard.jsp&lt;/result&gt;
* &lt;/action&gt;
* &lt;/package&gt;
* <!-- END SNIPPET: example --></pre>
*
* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
*/
public class ActionChainResult implements Result {
private static final Logger LOG = 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 <code>namespace/action!method</code> keys.
*/
public static LinkedList<String> getChainHistory() {
LinkedList<String> chainHistory = (LinkedList<String>) ActionContext.getContext().get(CHAIN_HISTORY);
// Add if not exists
if (chainHistory == null) {
chainHistory = new LinkedList<String>();
ActionContext.getContext().put(CHAIN_HISTORY, chainHistory);
}
return chainHistory;
}
/**
* @param invocation the DefaultActionInvocation calling the action call stack
*/
public void execute(ActionInvocation invocation) throws Exception {
// if the finalNamespace wasn't explicitly defined, assume the current one
if (this.namespace == null) {
this.namespace = invocation.getProxy().getNamespace();
}
ValueStack stack = ActionContext.getContext().getValueStack();
String finalNamespace = TextParseUtil.translateVariables(namespace, stack);
String finalActionName = TextParseUtil.translateVariables(actionName, stack);
String finalMethodName = this.methodName != null
? TextParseUtil.translateVariables(this.methodName, stack)
: null;
if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) {
addToHistory(finalNamespace, finalActionName, finalMethodName);
throw new XWorkException("Infinite recursion detected: "
+ ActionChainResult.getChainHistory().toString());
}
if (ActionChainResult.getChainHistory().isEmpty() && invocation != null && invocation.getProxy() != null) {
addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod());
}
addToHistory(finalNamespace, finalActionName, finalMethodName);
HashMap<String, Object> extraContext = new HashMap<String, Object>();
extraContext.put(ActionContext.VALUE_STACK, ActionContext.getContext().getValueStack());
extraContext.put(ActionContext.PARAMETERS, ActionContext.getContext().getParameters());
extraContext.put(CHAIN_HISTORY, ActionChainResult.getChainHistory());
if (LOG.isDebugEnabled()) {
LOG.debug("Chaining to action " + finalActionName);
}
proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext);
proxy.execute();
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ActionChainResult that = (ActionChainResult) o;
if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) return false;
if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false;
if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false;
return true;
}
@Override public int hashCode() {
int result;
result = (actionName != null ? actionName.hashCode() : 0);
result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
return result;
}
private boolean isInChainHistory(String namespace, String actionName, String methodName) {
LinkedList<? extends String> chainHistory = ActionChainResult.getChainHistory();
if (chainHistory == null) {
return false;
} else {
// Actions to skip
Set<String> skipActionsList = new HashSet<String>();
if (skipActions != null && skipActions.length() > 0) {
ValueStack stack = ActionContext.getContext().getValueStack();
String finalSkipActions = TextParseUtil.translateVariables(this.skipActions, stack);
skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions));
}
if (!skipActionsList.contains(actionName)) {
// Get if key is in the chain history
return chainHistory.contains(makeKey(namespace, actionName, methodName));
}
return false;
}
}
private void addToHistory(String namespace, String actionName, String methodName) {
List<String> chainHistory = ActionChainResult.getChainHistory();
chainHistory.add(makeKey(namespace, actionName, methodName));
}
private String makeKey(String namespace, String actionName, String methodName) {
if (null == methodName) {
return namespace + "/" + actionName;
}
return namespace + "/" + actionName + "!" + methodName;
}
}
@@ -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. <p>
* <p/>
* The ActionContext is thread local which means that values stored in the ActionContext are
* unique per thread. See the {@link ThreadLocal} class for more information. The benefit of
* this is you don't need to worry about a user specific action context, you just get it:
* <p/>
* <ul><code>ActionContext context = ActionContext.getContext();</code></ul>
* <p/>
* Finally, because of the thread local usage you don't need to worry about making your actions thread safe.
*
* @author Patrick Lightbody
* @author Bill Lynch (docs)
*/
public class ActionContext implements Serializable {
static ThreadLocal actionContext = 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<String, Object> context;
/**
* Creates a new ActionContext initialized with another context.
*
* @param context a context map.
*/
public ActionContext(Map<String, Object> context) {
this.context = context;
}
/**
* Sets the action invocation (the execution state).
*
* @param actionInvocation the action execution state.
*/
public void setActionInvocation(ActionInvocation actionInvocation) {
put(ACTION_INVOCATION, actionInvocation);
}
/**
* Gets the action invocation (the execution state).
*
* @return the action invocation (the execution state).
*/
public ActionInvocation getActionInvocation() {
return (ActionInvocation) get(ACTION_INVOCATION);
}
/**
* Sets the action's application context.
*
* @param application the action's application context.
*/
public void setApplication(Map<String, Object> application) {
put(APPLICATION, application);
}
/**
* Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise.
*
* @return a Map of ServletContext or generic application level Map
*/
public Map<String, Object> getApplication() {
return (Map<String, Object>) get(APPLICATION);
}
/**
* Sets the action context for the current thread.
*
* @param context the action context.
*/
public static void setContext(ActionContext context) {
actionContext.set(context);
}
/**
* Returns the ActionContext specific to the current thread.
*
* @return the ActionContext for the current thread, is never <tt>null</tt>.
*/
public static ActionContext getContext() {
return (ActionContext) 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<String, Object> contextMap) {
getContext().context = contextMap;
}
/**
* Gets the context map.
*
* @return the context map.
*/
public Map<String, Object> getContextMap() {
return context;
}
/**
* Sets conversion errors which occurred when executing the action.
*
* @param conversionErrors a Map of errors which occurred when executing the action.
*/
public void setConversionErrors(Map<String, Object> conversionErrors) {
put(CONVERSION_ERRORS, conversionErrors);
}
/**
* Gets the map of conversion errors which occurred when executing the action.
*
* @return the map of conversion errors which occurred when executing the action or an empty map if
* there were no errors.
*/
public Map<String, Object> getConversionErrors() {
Map<String, Object> errors = (Map) get(CONVERSION_ERRORS);
if (errors == null) {
errors = new HashMap<String, Object>();
setConversionErrors(errors);
}
return errors;
}
/**
* Sets the Locale for the current action.
*
* @param locale the Locale for the current action.
*/
public void setLocale(Locale locale) {
put(LOCALE, locale);
}
/**
* Gets the Locale of the current action. If no locale was ever specified the platform's
* {@link java.util.Locale#getDefault() default locale} is used.
*
* @return the Locale of the current action.
*/
public Locale getLocale() {
Locale locale = (Locale) get(LOCALE);
if (locale == null) {
locale = Locale.getDefault();
setLocale(locale);
}
return locale;
}
/**
* Sets the name of the current Action in the ActionContext.
*
* @param name the name of the current action.
*/
public void setName(String name) {
put(ACTION_NAME, name);
}
/**
* Gets the name of the current Action.
*
* @return the name of the current action.
*/
public String getName() {
return (String) get(ACTION_NAME);
}
/**
* Sets the action parameters.
*
* @param parameters the parameters for the current action.
*/
public void setParameters(Map<String, Object> parameters) {
put(PARAMETERS, parameters);
}
/**
* Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of
* parameters otherwise.
*
* @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a
* generic Map of parameters otherwise.
*/
public Map<String, Object> getParameters() {
return (Map<String, Object>) get(PARAMETERS);
}
/**
* Sets a map of action session values.
*
* @param session the session values.
*/
public void setSession(Map<String, Object> session) {
put(SESSION, session);
}
/**
* Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
*
* @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
*/
public Map<String, Object> getSession() {
return (Map<String, Object>) get(SESSION);
}
/**
* Sets the OGNL value stack.
*
* @param stack the OGNL value stack.
*/
public void setValueStack(ValueStack stack) {
put(VALUE_STACK, stack);
}
/**
* Gets the OGNL value stack.
*
* @return the OGNL value stack.
*/
public ValueStack getValueStack() {
return (ValueStack) get(VALUE_STACK);
}
/**
* Gets the container for this request
*
* @param cont The container
*/
public void setContainer(Container cont) {
put(CONTAINER, cont);
}
/**
* Sets the container for this request
*
* @return The container
*/
public Container getContainer() {
return (Container) get(CONTAINER);
}
public <T> T getInstance(Class<T> type) {
Container cont = getContainer();
if (cont != null) {
return cont.getInstance(type);
} else {
throw new XWorkException("Cannot find an initialized container for this request.");
}
}
/**
* Returns a value that is stored in the current ActionContext by doing a lookup using the value's key.
*
* @param key the key used to find the value.
* @return the value that was found using the key or <tt>null</tt> if the key was not found.
*/
public Object get(String key) {
return context.get(key);
}
/**
* Stores a value in the current ActionContext. The value can be looked up using the key.
*
* @param key the key of the value.
* @param value the value to be stored.
*/
public void put(String key, Object value) {
context.put(key, value);
}
}
@@ -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);
}
@@ -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 <code>invoke()</code> method, initially by the {@link ActionProxy}, then by the Interceptors, the
* Interceptors are all executed, and then the {@link Action} and the {@link Result}.
*
* @author Jason Carreira
* @see com.opensymphony.xwork2.ActionProxy
*/
public interface ActionInvocation extends Serializable {
/**
* Get the Action associated with this ActionInvocation.
*
* @return the Action
*/
Object getAction();
/**
* Gets whether this ActionInvocation has executed before.
* This will be set after the Action and the Result have executed.
*
* @return <tt>true</tt> if this ActionInvocation has executed before.
*/
boolean isExecuted();
/**
* Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
* responsible for setting this ActionContext onto the ThreadLocal before invoking
* the ActionInvocation and resetting the old ActionContext afterwards.
*
* @return the ActionContext.
*/
ActionContext getInvocationContext();
/**
* Get the ActionProxy holding this ActionInvocation.
*
* @return the ActionProxy.
*/
ActionProxy getProxy();
/**
* If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
* will walk down the chain of <code>ActionChainResult</code>s until it finds a non-chain result, which will be returned. If the
* ActionInvocation's result has not been executed before, the Result instance will be created and populated with
* the result params.
*
* @return the result.
* @throws Exception can be thrown.
*/
Result getResult() throws Exception;
/**
* Gets the result code returned from this ActionInvocation.
*
* @return the result code
*/
String getResultCode();
/**
* Sets the result code, possibly overriding the one returned by the
* action.
* <p/>
* The "intended" purpose of this method is to allow PreResultListeners to
* override the result code returned by the Action.
* <p/>
* If this method is used before the Action executes, the Action's returned
* result code will override what was set. However the Action could (if
* specifically coded to do so) inspect the ActionInvocation to see that
* someone "upstream" (e.g. an Interceptor) had suggested a value as the
* result, and it could therefore return the same value itself.
* <p/>
* If this method is called between the Action execution and the Result
* execution, then the value set here will override the result code the
* action had returned. Creating an Interceptor that implements
* {@link PreResultListener} will give you this oportunity.
* <p/>
* If this method is called after the Result has been executed, it will
* have the effect of raising an IllegalStateException.
*
* @param resultCode the result code.
* @throws IllegalStateException if called after the Result has been executed.
* @see #isExecuted()
*/
void setResultCode(String resultCode);
/**
* Gets the ValueStack associated with this ActionInvocation.
*
* @return the ValueStack
*/
ValueStack getStack();
/**
* Register a {@link PreResultListener} to be notified after the Action is executed and
* before the Result is executed.
* <p/>
* The ActionInvocation implementation must guarantee that listeners will be called in
* the order in which they are registered.
* <p/>
* Listener registration and execution does not need to be thread-safe.
*
* @param listener the listener to add.
*/
void addPreResultListener(PreResultListener listener);
/**
* Invokes the next step in processing this ActionInvocation.
* <p/>
* If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit
* ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor
* to execute. If there are no more Interceptors to be applied, the Action is executed.
* If the {@link ActionProxy#getExecuteResult()} method returns <tt>true</tt>, the Result is also executed.
*
* @throws Exception can be thrown.
* @return the return code.
*/
String invoke() throws Exception;
/**
* Invokes only the Action (not Interceptors or Results).
* <p/>
* This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
* being manipulated for certain functionality.
*
* @return the return code.
* @throws Exception can be thrown.
*/
String invokeActionOnly() throws Exception;
/**
* Sets the action event listener to respond to key action events.
*
* @param listener the listener.
*/
void setActionEventListener(ActionEventListener listener);
void init(ActionProxy proxy) ;
}
@@ -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.
* <p/>
* An example of this would be a remote proxy, where the layer between XWork and the action might be RMI or SOAP.
*
* @author Jason Carreira
*/
public interface ActionProxy {
/**
* Gets the Action instance for this Proxy.
*
* @return the Action instance
*/
Object getAction();
/**
* Gets the alias name this ActionProxy is mapped to.
*
* @return the alias name
*/
String getActionName();
/**
* Gets the ActionConfig this ActionProxy is built from.
*
* @return the ActionConfig
*/
ActionConfig getConfig();
/**
* Sets whether this ActionProxy should also execute the Result after executing the Action.
*
* @param executeResult <tt>true</tt> to also execute the Result.
*/
void setExecuteResult(boolean executeResult);
/**
* Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed.
*
* @return the status
*/
boolean getExecuteResult();
/**
* Gets the ActionInvocation associated with this ActionProxy.
*
* @return the ActionInvocation
*/
ActionInvocation getInvocation();
/**
* Gets the namespace the ActionConfig for this ActionProxy is mapped to.
*
* @return the namespace
*/
String getNamespace();
/**
* Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
* ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
*
* @return the result code returned from executing the ActionInvocation
* @throws Exception can be thrown.
* @see ActionInvocation
*/
String execute() throws Exception;
/**
* Gets the method name to execute, or <tt>null</tt> if no method has been specified (meaning <code>execute</code> will be invoked).
*
* @return the method to execute
*/
String getMethod();
}
@@ -0,0 +1,107 @@
/*
* Copyright 2002-2007,2009 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.opensymphony.xwork2;
import java.util.Map;
/**
* The {@link ActionProxyFactory} is used to create {@link ActionProxy}s to be executed.
* <p/>
* It is the entry point to XWork that is used by a dispatcher to create an {@link ActionProxy} to execute
* for a particular namespace and action name.
*
* @author Jason Carreira
* @see DefaultActionProxyFactory
*/
public interface ActionProxyFactory {
/**
* Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
* should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
* <p/>
* <b>Note:</b> This is the most used create method.
*
* @param namespace the namespace of the action, can be <tt>null</tt>
* @param actionName the name of the action
* @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
* @return ActionProxy the created action proxy
* @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map) instead}
*/
@Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map<String, Object> extraContext);
/**
* Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
* should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
* <p/>
* <b>Note:</b> This is the most used create method.
*
* @param namespace the namespace of the action, can be <tt>null</tt>
* @param actionName the name of the action
* @param methodName the name of the method to execute
* @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
* @return ActionProxy the created action proxy
* @since 2.1.1
*/
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext);
/**
* Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
* should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
*
* @param namespace the namespace of the action, can be <tt>null</tt>
* @param actionName the name of the action
* @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
* @param executeResult flag which tells whether the result should be executed after the action
* @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
* @return ActionProxy the created action proxy
* @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map,boolean,boolean)} instead
*/
@Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext);
/**
* Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
* should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
*
* @param namespace the namespace of the action, can be <tt>null</tt>
* @param actionName the name of the action
* @param methodName the name of the method to execute
* @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
* @param executeResult flag which tells whether the result should be executed after the action
* @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
* @return ActionProxy the created action proxy
* @since 2.1.1
*/
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext);
/**
* Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
* should be fully initialized when it is returned, including passed {@link ActionInvocation} instance.
*
* @param actionInvocation the action invocation instance to associate with
* @param namespace the namespace of the action, can be <tt>null</tt>
* @param actionName the name of the action
* @param methodName the name of the method to execute
* @param executeResult flag which tells whether the result should be executed after the action
* @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
* @return ActionProxy the created action proxy
* @since 2.1.1
*/
public ActionProxy createActionProxy(ActionInvocation actionInvocation, String namespace, String actionName, String methodName,
boolean executeResult, boolean cleanupContext);
}
@@ -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<String> errorMessages) {
validationAware.setActionErrors(errorMessages);
}
public Collection<String> getActionErrors() {
return validationAware.getActionErrors();
}
public void setActionMessages(Collection<String> messages) {
validationAware.setActionMessages(messages);
}
public Collection<String> getActionMessages() {
return validationAware.getActionMessages();
}
/**
* @deprecated Use {@link #getActionErrors()}.
*/
@Deprecated
public Collection<String> getErrorMessages() {
return getActionErrors();
}
/**
* @deprecated Use {@link #getFieldErrors()}.
*/
@Deprecated
public Map<String, List<String>> getErrors() {
return getFieldErrors();
}
public void setFieldErrors(Map<String, List<String>> errorMap) {
validationAware.setFieldErrors(errorMap);
}
public Map<String, List<String>> getFieldErrors() {
return validationAware.getFieldErrors();
}
public Locale getLocale() {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
return ctx.getLocale();
} else {
LOG.debug("Action context not initialized");
return null;
}
}
public boolean hasKey(String key) {
return getTextProvider().hasKey(key);
}
public String getText(String aTextName) {
return getTextProvider().getText(aTextName);
}
public String getText(String aTextName, String defaultValue) {
return getTextProvider().getText(aTextName, defaultValue);
}
public String getText(String aTextName, String defaultValue, String obj) {
return getTextProvider().getText(aTextName, defaultValue, obj);
}
public String getText(String aTextName, List<Object> 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<Object> 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<Object> 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".
* <p/>
* Subclasses should override this method to provide their business logic.
* <p/>
* See also {@link com.opensymphony.xwork2.Action#execute()}.
*
* @return returns {@link #SUCCESS}
* @throws Exception can be thrown by subclasses.
*/
public String execute() throws Exception {
return SUCCESS;
}
public boolean hasActionErrors() {
return validationAware.hasActionErrors();
}
public boolean hasActionMessages() {
return validationAware.hasActionMessages();
}
public boolean hasErrors() {
return validationAware.hasErrors();
}
public boolean hasFieldErrors() {
return validationAware.hasFieldErrors();
}
/**
* Clears field errors. Useful for Continuations and other situations
* where you might want to clear parts of the state on the same action.
*/
public void clearFieldErrors() {
validationAware.clearFieldErrors();
}
/**
* Clears action errors. Useful for Continuations and other situations
* where you might want to clear parts of the state on the same action.
*/
public void clearActionErrors() {
validationAware.clearActionErrors();
}
/**
* Clears messages. Useful for Continuations and other situations
* where you might want to clear parts of the state on the same action.
*/
public void clearMessages() {
validationAware.clearMessages();
}
/**
* Clears all errors. Useful for Continuations and other situations
* where you might want to clear parts of the state on the same action.
*/
public void clearErrors() {
validationAware.clearErrors();
}
/**
* Clears all errors and messages. Useful for Continuations and other situations
* where you might want to clear parts of the state on the same action.
*/
public void clearErrorsAndMessages() {
validationAware.clearErrorsAndMessages();
}
/**
* A default implementation that validates nothing.
* Subclasses should override this method to provide validations.
*/
public void validate() {
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* <!-- START SNIPPET: pause-method -->
* Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return
* the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc.
* <p/>
* <p/>
* The next time this action is invoked (and using the same continuation ID), the method will resume immediately
* after where this method was called, with the entire call stack in the execute method restored.
* <p/>
* <p/>
* Note: this method can <b>only</b> be called within the {@link #execute()} method.
* <!-- END SNIPPET: pause-method -->
*
* @param result the result to return - the same type of return value in the {@link #execute()} method.
*/
public void pause(String result) {
}
/**
* If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory},
* inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in,
* then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field
* for future references and at the returns reference to that field
*
* @return reference to field with TextProvider
*/
private TextProvider getTextProvider() {
if (textProvider == null) {
TextProviderFactory tpf = new TextProviderFactory();
if (container != null) {
container.inject(tpf);
}
textProvider = tpf.createInstance(getClass(), this);
}
return textProvider;
}
@Inject
public void setContainer(Container container) {
this.container = container;
}
}
@@ -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<TextProvider> textProviders = new ArrayList<TextProvider>();
/**
* Instantiates a {@link CompositeTextProvider} with some predefined <code>textProviders</code>.
*
* @param textProviders
*/
public CompositeTextProvider(List<TextProvider> textProviders) {
this.textProviders.addAll(textProviders);
}
/**
* Instantiates a {@link CompositeTextProvider} with some predefined <code>textProviders</code>.
*
* @param textProviders
*/
public CompositeTextProvider(TextProvider[] textProviders) {
this(Arrays.asList(textProviders));
}
/**
* @param key The key to lookup in ressource bundles.
* @return <tt>true</tt>, if the requested key is found in one of the ressource bundles.
* @see {@link com.opensymphony.xwork2.TextProvider#hasKey(String)}
* It will consult each individual {@link TextProvider}s and return true if either one of the
* {@link TextProvider} has such a <code>key></code> else false.
*/
public boolean hasKey(String key) {
// if there's a key in either text providers we are ok, else try the next text provider
for (TextProvider tp : textProviders) {
if (tp.hasKey(key)) {
return true;
}
}
return false;
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>
*
* @param key The key to lookup in 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
* <code>key</code> before returning <code>defaultValue</code> if every else fails.
*
* @param key
* @param defaultValue
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String)}
*/
public String getText(String key, String defaultValue) {
return getText(key, defaultValue, Collections.emptyList());
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>, before returining <code>defaultValue</code>
* if every else fails.
*
* @param key
* @param defaultValue
* @param obj
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String)}
*/
public String getText(String key, String defaultValue, final String obj) {
return getText(key, defaultValue, new ArrayList<Object>() {
{
add(obj);
}
});
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>.
*
* @param key
* @param args
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, java.util.List)}
*/
public String getText(String key, List<Object> args) {
return getText(key, key, args);
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>.
*
* @param key
* @param args
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String[])}
*/
public String getText(String key, String[] args) {
return getText(key, key, args);
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>, before returining <code>defaultValue</code>
*
* @param key
* @param defaultValue
* @param args
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText#getText(String, String, java.util.List)}
*/
public String getText(String key, String defaultValue, List<Object> args) {
// if there's one text provider that gives us a msg not the same as defaultValue
// for this key, we are ok, else try the next
// text provider
for (TextProvider textProvider : textProviders) {
String msg = textProvider.getText(key, defaultValue, args);
if (msg != null && (!msg.equals(defaultValue))) {
return msg;
}
}
return defaultValue;
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>, before returining <code>defaultValue</code>.
*
* @param key
* @param defaultValue
* @param args
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[])}
*/
public String getText(String key, String defaultValue, String[] args) {
// if there's one text provider that gives us a msg not the same as defaultValue
// for this key, we are ok, else try the next
// text provider
for (TextProvider textProvider : textProviders) {
String msg = textProvider.getText(key, defaultValue, args);
if (msg != null && (!msg.equals(defaultValue))) {
return msg;
}
}
return defaultValue;
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>, before returining <code>defaultValue</code>
*
* @param key
* @param defaultValue
* @param args
* @param stack
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, java.util.List, com.opensymphony.xwork2.util.OgnlValueStack)}
*/
public String getText(String key, String defaultValue, List<Object> args, ValueStack stack) {
// if there's one text provider that gives us a msg not the same as defaultValue
// for this key, we are ok, else try the next
// text provider
for (TextProvider textProvider : textProviders) {
String msg = textProvider.getText(key, defaultValue, args, stack);
if (msg != null && (!msg.equals(defaultValue))) {
return msg;
}
}
return defaultValue;
}
/**
* It will consult each {@link TextProvider}s and return the first valid message for this
* <code>key</code>, before returining <code>defaultValue</code>
*
* @param key
* @param defaultValue
* @param args
* @param stack
* @return
* @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[], com.opensymphony.xwork2.util.ValueStack)}
*/
public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
// if there's one text provider that gives us a msg not the same as defaultValue
// for this key, we are ok, else try the next
// text provider
for (TextProvider textProvider : textProviders) {
String msg = textProvider.getText(key, defaultValue, args, stack);
if (msg != null && (!msg.equals(defaultValue))) {
return msg;
}
}
return defaultValue;
}
/**
* It will consult each {@link TextProvider}s and return the first non-null {@link ResourceBundle}.
*
* @param bundleName
* @return
* @see {@link TextProvider#getTexts(String)}
*/
public ResourceBundle getTexts(String bundleName) {
// if there's one text provider that gives us a non-null resource 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;
}
}
@@ -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<PreResultListener> preResultListeners;
protected Map<String, Object> extraContext;
protected ActionContext invocationContext;
protected Iterator<InterceptorMapping> interceptors;
protected ValueStack stack;
protected Result result;
protected Result explicitResult;
protected String resultCode;
protected boolean executed = false;
protected boolean pushAction = true;
protected ObjectFactory objectFactory;
protected ActionEventListener actionEventListener;
protected ValueStackFactory valueStackFactory;
protected Container container;
private Configuration configuration;
protected UnknownHandlerManager unknownHandlerManager;
public DefaultActionInvocation(final Map<String, Object> 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<PreResultListener>(1);
}
preResultListeners.add(listener);
}
public Result createResult() throws Exception {
if (explicitResult != null) {
Result ret = explicitResult;
explicitResult = null;
return ret;
}
ActionConfig config = proxy.getConfig();
Map<String, ResultConfig> results = config.getResults();
ResultConfig resultConfig = null;
try {
resultConfig = results.get(resultCode);
} catch (NullPointerException e) {
// 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<String, Object> contextMap) {
// load action
String timerKey = "actionCreate: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
} catch (InstantiationException e) {
throw new XWorkException("Unable to 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<String, Object> createContextMap() {
Map<String, Object> contextMap;
if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) {
// In case the ValueStack was passed in
stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK);
if (stack == null) {
throw new IllegalStateException("There was a null Stack set into the extra params.");
}
contextMap = stack.getContext();
} else {
// create the value stack
// this also adds the ValueStack to its context
stack = valueStackFactory.createValueStack();
// create the action context
contextMap = stack.getContext();
}
// put extraContext in
if (extraContext != null) {
contextMap.putAll(extraContext);
}
//put this DefaultActionInvocation into the context map
contextMap.put(ActionContext.ACTION_INVOCATION, this);
contextMap.put(ActionContext.CONTAINER, container);
return contextMap;
}
/**
* Uses getResult to get the final Result and executes it
*
* @throws ConfigurationException If not result can be found with the returned code
*/
private void executeResult() throws Exception {
result = createResult();
String timerKey = "executeResult: " + getResultCode();
try {
UtilTimerStack.push(timerKey);
if (result != null) {
result.execute(this);
} else if (resultCode != null && !Action.NONE.equals(resultCode)) {
throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
+ " and result " + getResultCode(), proxy.getConfig());
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
}
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
public void init(ActionProxy proxy) {
this.proxy = proxy;
Map<String, Object> contextMap = createContextMap();
// Setting this so that other classes, like object factories, can use the ActionProxy and other
// contextual information to operate
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.setActionInvocation(this);
}
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());
// get a new List so we don't get problems with the iterator if someone changes the list
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(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);
}
}
}
@@ -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 <a href="mailto:hu_pengfei@yahoo.com.cn">Henry Hu</a>
* @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.
* <p/>
* 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);
}
}
}
@@ -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<String, Object> extraContext) {
return createActionProxy(namespace, actionName, null, extraContext, true, true);
}
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext) {
return createActionProxy(namespace, actionName, methodName, extraContext, true, true);
}
public ActionProxy createActionProxy(String namespace, String actionName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
return createActionProxy(namespace, actionName, null, extraContext, executeResult, cleanupContext);
}
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> 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;
}
}
@@ -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 <jcarreira@gmail.com>
* @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<Object> 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<Object> 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<Object> args = new ArrayList<Object>(1);
args.add(obj);
return getText(key, defaultValue, args);
}
public String getText(String key, String defaultValue, List<Object> 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<Object> values = new ArrayList<Object>(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;
}
}
@@ -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<UnknownHandler> 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<UnknownHandlerConfig> unkownHandlerStack = configuration.getUnknownHandlerStack();
unknownHandlers = new ArrayList<UnknownHandler>();
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<String> 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<UnknownHandler> getUnknownHandlers() {
return unknownHandlers;
}
}
@@ -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;
/**
* <code>InvalidMetadataException</code>
*
* @author Rainer Hermanns
* @version $Id$
*/
public class InvalidMetadataException extends RuntimeException {
/**
* Create a new <code>InvalidMetadataException</code> with the supplied error message.
*
* @param msg the error message
*/
public InvalidMetadataException(String msg) {
super(msg);
}
}
@@ -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}.
* <p/>
* 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();
}
@@ -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 {
}
@@ -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<T> {
/**
* Gets the model to be pushed onto the ValueStack instead of the Action itself.
*
* @return the model
*/
T getModel();
}
@@ -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.
* <p/>
* This default implementation uses the {@link #buildBean(Class,java.util.Map) buildBean}
* method to create all classes (interceptors, actions, results, etc).
* <p/>
*
* @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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, String> interceptorRefParams) throws ConfigurationException {
String interceptorClassName = interceptorConfig.getClassName();
Map<String, String> thisInterceptorClassParams = interceptorConfig.getParams();
Map<String, String> params = (thisInterceptorClassParams == null) ? new HashMap<String, String>() : new HashMap<String, String>(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<String, Object> extraContext) throws Exception {
String resultClassName = resultConfig.getClassName();
Result result = null;
if (resultClassName != null) {
result = (Result) buildBean(resultClassName, extraContext);
Map<String, String> params = resultConfig.getParams();
if (params != null) {
for (Map.Entry<String, String> 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<String, String> params, Map<String, Object> extraContext) throws Exception {
Validator validator = (Validator) buildBean(className, null);
reflectionProvider.setProperties(params, validator);
return validator;
}
static class ContinuationsClassLoader extends ClassLoader {
}
}
@@ -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 <code>prepare()</code> 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;
}
@@ -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);
}
@@ -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 <code>Action.NONE</code>) of an {@link Action} are mapped to a View implementation.
* <p/>
* Examples of Views might be:
* <ul>
* <li>SwingPanelView - pops up a new Swing panel</li>
* <li>ActionChainView - executes another action</li>
* <li>SerlvetRedirectView - redirects the HTTP response to a URL</li>
* <li>ServletDispatcherView - dispatches the HTTP response to a URL</li>
* </ul>
*
* @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;
}
@@ -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);
}
}
@@ -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.
* <p/>
* 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.
* <p/>
* When you want to use your own implementation for Struts 2 project you have to define following
* bean and constant in struts.xml:
* &lt;bean class=&quot;org.demo.MyTextProvider&quot; name=&quot;myTextProvider&quot; type=&quot;com.opensymphony.xwork2.TextProvider&quot; /&gt;
* &lt;constant name=&quot;struts.xworkTextProvider&quot; value=&quot;myTextProvider&quot; /&gt;
* <p/>
* if you want to also use your implemntation for framework's messages define another constant (remember to put
* into it all framework messages)
* &lt;constant name=&quot;system&quot; value=&quot;myTextProvider&quot; /&gt;
* <p/>
* 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<Object> 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<Object> 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<Object> 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 <code>"com/acme/Foo"</code>.
* @return the bundle
*/
ResourceBundle getTexts(String bundleName);
/**
* Get the resource bundle associated with the implementing class (usually an action).
*
* @return the bundle
*/
ResourceBundle getTexts();
}
@@ -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;
}
}
@@ -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<Object> args = new ArrayList<Object>();
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<Object> 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<Object> 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<Object> 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.
* <p/>
* 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();
}
}
@@ -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 <b>not</b> have its properties copied during chaining.
*
* @see com.opensymphony.xwork2.interceptor.ChainingInterceptor
*/
public interface Unchainable {
}
@@ -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.
* <p/>
* 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 <tt>null</tt>
* @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 <tt>null</tt>
* @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;
}
@@ -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<UnknownHandler> getUnknownHandlers();
}
@@ -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();
}
@@ -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<String> 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<String> getActionErrors();
/**
* Set the Collection of Action-level String messages (not errors).
*
* @param messages Collection of String messages (not errors).
*/
void setActionMessages(Collection<String> 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<String> getActionMessages();
/**
* Set the field error map of fieldname (String) to Collection of String error messages.
*
* @param errorMap field error map
*/
void setFieldErrors(Map<String, List<String>> 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<String, List<String>> 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.
* <p/>
* <b>Note</b>: that this does not have the same meaning as in WW 1.x.
*
* @return <code>(hasActionErrors() || hasFieldErrors())</code>
*/
boolean hasErrors();
/**
* Check whether there are any field errors associated with this action.
*
* @return whether there are any field errors
*/
boolean hasFieldErrors();
}
@@ -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<String> actionErrors;
private Collection<String> actionMessages;
private Map<String, List<String>> fieldErrors;
public synchronized void setActionErrors(Collection<String> errorMessages) {
this.actionErrors = errorMessages;
}
public synchronized Collection<String> getActionErrors() {
return new ArrayList<String>(internalGetActionErrors());
}
public synchronized void setActionMessages(Collection<String> messages) {
this.actionMessages = messages;
}
public synchronized Collection<String> getActionMessages() {
return new ArrayList<String>(internalGetActionMessages());
}
public synchronized void setFieldErrors(Map<String, List<String>> errorMap) {
this.fieldErrors = errorMap;
}
public synchronized Map<String, List<String>> getFieldErrors() {
return new LinkedHashMap<String, List<String>>(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<String, List<String>> errors = internalGetFieldErrors();
List<String> thisFieldErrors = errors.get(fieldName);
if (thisFieldErrors == null) {
thisFieldErrors = new ArrayList<String>();
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<String> internalGetActionErrors() {
if (actionErrors == null) {
actionErrors = new ArrayList<String>();
}
return actionErrors;
}
private Collection<String> internalGetActionMessages() {
if (actionMessages == null) {
actionMessages = new ArrayList<String>();
}
return actionMessages;
}
private Map<String, List<String>> internalGetFieldErrors() {
if (fieldErrors == null) {
fieldErrors = new LinkedHashMap<String, List<String>>();
}
return fieldErrors;
}
/**
* Clears field errors map.
* <p/>
* Will clear the map that contains field errors.
*/
public synchronized void clearFieldErrors() {
internalGetFieldErrors().clear();
}
/**
* Clears action errors list.
* <p/>
* Will clear the list that contains action errors.
*/
public synchronized void clearActionErrors() {
internalGetActionErrors().clear();
}
/**
* Clears messages list.
* <p/>
* Will clear the list that contains action messages.
*/
public synchronized void clearMessages() {
internalGetActionMessages().clear();
}
/**
* Clears all error list/maps.
* <p/>
* 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.
* <p/>
* Will clear the maps/lists that contain
* field errors, action errors and action messages.
*/
public synchronized void clearErrorsAndMessages() {
internalGetFieldErrors().clear();
internalGetActionErrors().clear();
internalGetActionMessages().clear();
}
}
@@ -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<String, Object> 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<String, Object> 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);
}
}
}
@@ -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 <code>XWorkException</code> with no detail message.
*/
public XWorkException() {
}
/**
* Constructs a <code>XWorkException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public XWorkException(String s) {
this(s, null, null);
}
/**
* Constructs a <code>XWorkException</code> 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 <code>XWorkException</code> with the root cause
*
* @param cause The wrapped exception
*/
public XWorkException(Throwable cause) {
this(null, cause, null);
}
/**
* Constructs a <code>XWorkException</code> 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 <code>XWorkException</code> 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 <code>XWorkException</code> 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, <tt>null</tt> if no cause
* @deprecated Use {@link #getCause()}
*/
@Deprecated public Throwable getThrowable() {
return getCause();
}
/**
* Gets the location of the error, if available
*
* @return the location, <tt>null</tt> 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 <code>Throwable</code>.
*/
@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;
}
}
}
@@ -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";
}
@@ -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);
}
});
}
}
@@ -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<String> getPackageConfigNames();
Map<String, PackageConfig> 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<ConfigurationProvider> providers) throws ConfigurationException;
/**
* @since 2.1
* @param containerProviders
* @throws ConfigurationException
*/
List<PackageProvider> reloadContainer(List<ContainerProvider> containerProviders) throws ConfigurationException;
/**
* @return the container
*/
Container getContainer();
Set<String> getLoadedFileNames();
/**
* @since 2.1
* @return list of unknown handlers
*/
List<UnknownHandlerConfig> getUnknownHandlerStack();
/**
* @since 2.1
* @param unknownHandlerStack
*/
void setUnknownHandlerStack(List<UnknownHandlerConfig> unknownHandlerStack);
}
@@ -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 <code>ConfigurationException</code> with no detail message.
*/
public ConfigurationException() {
}
/**
* Constructs a <code>ConfigurationException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public ConfigurationException(String s) {
super(s);
}
/**
* Constructs a <code>ConfigurationException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public ConfigurationException(String s, Object target) {
super(s, target);
}
/**
* Constructs a <code>ConfigurationException</code> with no detail message.
*/
public ConfigurationException(Throwable cause) {
super(cause);
}
/**
* Constructs a <code>ConfigurationException</code> with no detail message.
*/
public ConfigurationException(Throwable cause, Object target) {
super(cause, target);
}
/**
* Constructs a <code>ConfigurationException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public ConfigurationException(String s, Throwable cause) {
super(s, cause);
}
/**
* Constructs a <code>ConfigurationException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public ConfigurationException(String s, Throwable cause, Object target) {
super(s, cause, target);
}
}
@@ -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<ContainerProvider> containerProviders = new CopyOnWriteArrayList<ContainerProvider>();
private List<PackageProvider> packageProviders = new CopyOnWriteArrayList<PackageProvider>();
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.
* </p>
* <p/>
* 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<ConfigurationProvider> getConfigurationProviders() {
List<ContainerProvider> contProviders = getContainerProviders();
List<ConfigurationProvider> providers = new ArrayList<ConfigurationProvider>();
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.
* </p>
* <p/>
* 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<ContainerProvider> 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<ConfigurationProvider> configurationProviders) {
// Silly copy necessary due to lack of ability to cast generic lists
List<ContainerProvider> contProviders = new ArrayList<ContainerProvider>();
contProviders.addAll(configurationProviders);
setContainerProviders(contProviders);
}
/**
* Set the list of configuration providers
*
* @param containerProviders
*/
public void setContainerProviders(List<ContainerProvider> containerProviders) {
providerLock.lock();
try {
this.containerProviders = new CopyOnWriteArrayList<ContainerProvider>(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<ContainerProvider>();
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<ContainerProvider> 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());
}
}
@@ -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 {
}
@@ -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<PackageConfig> buildParentsFromString(Configuration configuration, String parent) {
if ((parent == null) || ("".equals(parent))) {
return Collections.emptyList();
}
StringTokenizer tokenizer = new StringTokenizer(parent, ", ");
List<PackageConfig> parents = new ArrayList<PackageConfig>();
while (tokenizer.hasMoreTokens()) {
String parentName = tokenizer.nextToken().trim();
if (!"".equals(parentName)) {
PackageConfig parentPackageContext = configuration.getPackageConfig(parentName);
if (parentPackageContext != null) {
parents.add(parentPackageContext);
}
}
}
return parents;
}
}
@@ -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 <tt>true</tt>, whether the ContainerProvider should reload its configuration, <tt>false</tt>otherwise.
*/
public boolean needsReload();
/**
* Registers beans and properties for the Container
*
* @param builder The builder to register beans with
* @param props The properties to register constants with
* @throws ConfigurationException If anything goes wrong
*/
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException;
}
@@ -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 <tt>true</tt>, whether the PackageProvider should reload its configuration, <tt>false</tt>otherwise.
*/
public boolean needsReload();
/**
* Loads the packages for the configuration.
* @throws ConfigurationException
*/
public void loadPackages() throws ConfigurationException;
}
@@ -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);
}
}
@@ -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
* <pre>
* ActionConfig config = (ActionConfig)((Map)getActionConfigs.get(namespace)).get(name);
* </pre>
* should return a valid config for valid namespace/name pairs
*/
Map<String, Map<String, ActionConfig>> getActionConfigs();
}
@@ -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:
* <ul>
* <li>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</li>
* <li>clazz - the class name for the action</li>
* <li>params - the params to be set for this action just before execution</li>
* <li>results - the result map {String -> View class}</li>
* <li>resultParameters - params for results {String -> Map}</li>
* <li>typeConverter - the Ognl TypeConverter to use when getting/setting properties</li>
* </ul>
*
* @author Mike
* @author Rainer Hermanns
* @version $Revision$
*/
public class ActionConfig extends Located implements Serializable {
public static final String WILDCARD = "*";
protected List<InterceptorMapping> interceptors; // a list of interceptorMapping Objects eg. List<InterceptorMapping>
protected Map<String,String> params;
protected Map<String, ResultConfig> results;
protected List<ExceptionMappingConfig> exceptionMappings;
protected String className;
protected String methodName;
protected String packageName;
protected String name;
protected Set<String> allowedMethods;
protected ActionConfig(String packageName, String name, String className) {
this.packageName = packageName;
this.name = name;
this.className = className;
params = new LinkedHashMap<String, String>();
results = new LinkedHashMap<String, ResultConfig>();
interceptors = new ArrayList<InterceptorMapping>();
exceptionMappings = new ArrayList<ExceptionMappingConfig>();
allowedMethods = new HashSet<String>();
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<String,String>(orig.params);
this.interceptors = new ArrayList<InterceptorMapping>(orig.interceptors);
this.results = new LinkedHashMap<String,ResultConfig>(orig.results);
this.exceptionMappings = new ArrayList<ExceptionMappingConfig>(orig.exceptionMappings);
this.allowedMethods = new HashSet<String>(orig.allowedMethods);
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
public List<ExceptionMappingConfig> getExceptionMappings() {
return exceptionMappings;
}
public List<InterceptorMapping> getInterceptors() {
return interceptors;
}
public Set<String> 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<String, String> getParams() {
return params;
}
public Map<String, ResultConfig> getResults() {
return results;
}
public boolean isAllowedMethod(String method) {
if (allowedMethods.size() == 1 && WILDCARD.equals(allowedMethods.iterator().next())) {
return true;
} else {
return allowedMethods.contains(method);
}
}
@Override public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ActionConfig)) {
return false;
}
final ActionConfig actionConfig = (ActionConfig) o;
if ((className != null) ? (!className.equals(actionConfig.className)) : (actionConfig.className != null)) {
return false;
}
if ((name != null) ? (!name.equals(actionConfig.name)) : (actionConfig.name != null)) {
return false;
}
if ((interceptors != null) ? (!interceptors.equals(actionConfig.interceptors)) : (actionConfig.interceptors != null))
{
return false;
}
if ((methodName != null) ? (!methodName.equals(actionConfig.methodName)) : (actionConfig.methodName != null)) {
return false;
}
if ((params != null) ? (!params.equals(actionConfig.params)) : (actionConfig.params != null)) {
return false;
}
if ((results != null) ? (!results.equals(actionConfig.results)) : (actionConfig.results != null)) {
return false;
}
if ((allowedMethods != null) ? (!allowedMethods.equals(actionConfig.allowedMethods)) : (actionConfig.allowedMethods != null)) {
return false;
}
return true;
}
@Override public int hashCode() {
int result;
result = (interceptors != null ? interceptors.hashCode() : 0);
result = 31 * result + (params != null ? params.hashCode() : 0);
result = 31 * result + (results != null ? results.hashCode() : 0);
result = 31 * result + (exceptionMappings != null ? exceptionMappings.hashCode() : 0);
result = 31 * result + (className != null ? className.hashCode() : 0);
result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (allowedMethods != null ? allowedMethods.hashCode() : 0);
return result;
}
@Override public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{ActionConfig ");
sb.append(name).append(" (");
sb.append(className);
if (methodName != null) {
sb.append(".").append(methodName).append("()");
}
sb.append(")");
sb.append(" - ").append(location);
sb.append("}");
return sb.toString();
}
/**
* The builder for this object. An instance of this object is the only way to construct a new instance. The
* purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
* After setting any values you need, call the {@link #build()} method to create the object.
*/
public static class Builder implements InterceptorListHolder{
private ActionConfig target;
public Builder(ActionConfig toClone) {
target = new ActionConfig(toClone);
}
public Builder(String packageName, String name, String className) {
target = new ActionConfig(packageName, name, className);
}
public Builder packageName(String name) {
target.packageName = name;
return this;
}
public Builder name(String name) {
target.name = name;
return this;
}
public Builder className(String name) {
target.className = name;
return this;
}
public Builder defaultClassName(String name) {
if (StringUtils.isEmpty(target.className)) {
target.className = name;
}
return this;
}
public Builder methodName(String method) {
target.methodName = method;
return this;
}
public Builder addExceptionMapping(ExceptionMappingConfig exceptionMapping) {
target.exceptionMappings.add(exceptionMapping);
return this;
}
public Builder addExceptionMappings(Collection<? extends ExceptionMappingConfig> mappings) {
target.exceptionMappings.addAll(mappings);
return this;
}
public Builder exceptionMappings(Collection<? extends ExceptionMappingConfig> mappings) {
target.exceptionMappings.clear();
target.exceptionMappings.addAll(mappings);
return this;
}
public Builder addInterceptor(InterceptorMapping interceptor) {
target.interceptors.add(interceptor);
return this;
}
public Builder addInterceptors(List<InterceptorMapping> interceptors) {
target.interceptors.addAll(interceptors);
return this;
}
public Builder interceptors(List<InterceptorMapping> 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<String,String> params) {
target.params.putAll(params);
return this;
}
public Builder addResultConfig(ResultConfig resultConfig) {
target.results.put(resultConfig.getName(), resultConfig);
return this;
}
public Builder addResultConfigs(Collection<ResultConfig> configs) {
for (ResultConfig rc : configs) {
target.results.put(rc.getName(), rc);
}
return this;
}
public Builder addResultConfigs(Map<String,ResultConfig> configs) {
target.results.putAll(configs);
return this;
}
public Builder addAllowedMethod(String methodName) {
target.allowedMethods.add(methodName);
return this;
}
public Builder addAllowedMethod(Collection<String> 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;
}
}
}
@@ -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<String,String> params;
protected ExceptionMappingConfig(String name, String exceptionClassName, String result) {
this.name = name;
this.exceptionClassName = exceptionClassName;
this.result = result;
this.params = new LinkedHashMap<String,String>();
}
protected ExceptionMappingConfig(ExceptionMappingConfig target) {
this.name = target.name;
this.exceptionClassName = target.exceptionClassName;
this.result = target.result;
this.params = new LinkedHashMap<String,String>(target.params);
}
public String getName() {
return name;
}
public String getExceptionClassName() {
return exceptionClassName;
}
public String getResult() {
return result;
}
public Map<String,String> 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<String,String> 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;
}
}
}
@@ -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.
* <p/>
* In the xml configuration file this is defined as the <code>interceptors</code> tag.
*
* @author Mike
*/
public class InterceptorConfig extends Located implements Serializable {
Map<String,String> params;
String className;
String name;
protected InterceptorConfig(String name, String className) {
this.params = new LinkedHashMap<String,String>();
this.name = name;
this.className = className;
}
protected InterceptorConfig(InterceptorConfig orig) {
this.name = orig.name;
this.className = orig.className;
this.params = new LinkedHashMap<String,String>(orig.params);
}
public String getClassName() {
return className;
}
public String getName() {
return name;
}
public Map<String,String> 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<String,String> 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;
}
}
}
@@ -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<InterceptorMapping> interceptors);
}
@@ -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);
}
@@ -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;
/**
* <code>InterceptorMapping</code>
*
* @author <a href="mailto:hermanns@aixcept.de">Rainer Hermanns</a>
* @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;
}
}
@@ -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.
* <p/>
* In the xml configuration file this is defined as the <code>interceptor-stack</code> 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<InterceptorMapping> interceptors;
private String name;
/**
* Creates an InterceptorStackConfig object.
*/
protected InterceptorStackConfig() {
this.interceptors = new ArrayList<InterceptorMapping>();
}
/**
* Creates an InterceptorStackConfig object with a particular <code>name</code>.
*
* @param name
*/
protected InterceptorStackConfig(InterceptorStackConfig orig) {
this.name = orig.name;
this.interceptors = new ArrayList<InterceptorMapping>(orig.interceptors);
}
/**
* Returns a <code>Collection</code> of InterceptorMapping objects.
*
* @return
*/
public Collection<InterceptorMapping> getInterceptors() {
return interceptors;
}
/**
* Get the name of this interceptor stack configuration.
*
* @return String
*/
public String getName() {
return name;
}
/**
* An InterceptorStackConfig object is equals with <code>o</code> only if
* <ul>
* <li>o is an InterceptorStackConfig object</li>
* <li>both names are equals</li>
* <li>all of their <code>InterceptorMapping</code>s are equals</li>
* </ul>
*/
@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 <code>InterceptorStackConfig</code>'s name and its
* <code>InterceptorMapping</code>s.
*/
@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 <code>InterceptorMapping</code> object.
*/
public Builder addInterceptor(InterceptorMapping interceptor) {
target.interceptors.add(interceptor);
return this;
}
/**
* Add a List of <code>InterceptorMapping</code> objects.
*/
public Builder addInterceptors(List<InterceptorMapping> 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;
}
}
}
@@ -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.
* <p/>
* In the xml configuration file this is defined as the <code>package</code> 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<String, ActionConfig> actionConfigs;
private Map<String, ResultConfig> globalResultConfigs;
private Map<String, Object> interceptorConfigs;
private Map<String, ResultTypeConfig> resultTypeConfigs;
private List<ExceptionMappingConfig> globalExceptionMappingConfigs;
private List<PackageConfig> 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<String, ActionConfig>();
globalResultConfigs = new LinkedHashMap<String, ResultConfig>();
interceptorConfigs = new LinkedHashMap<String, Object>();
resultTypeConfigs = new LinkedHashMap<String, ResultTypeConfig>();
globalExceptionMappingConfigs = new ArrayList<ExceptionMappingConfig>();
parents = new ArrayList<PackageConfig>();
}
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<String, ActionConfig>(orig.actionConfigs);
this.globalResultConfigs = new LinkedHashMap<String, ResultConfig>(orig.globalResultConfigs);
this.interceptorConfigs = new LinkedHashMap<String, Object>(orig.interceptorConfigs);
this.resultTypeConfigs = new LinkedHashMap<String, ResultTypeConfig>(orig.resultTypeConfigs);
this.globalExceptionMappingConfigs = new ArrayList<ExceptionMappingConfig>(orig.globalExceptionMappingConfigs);
this.parents = new ArrayList<PackageConfig>(orig.parents);
}
public boolean isAbstract() {
return isAbstract;
}
public Map<String, ActionConfig> 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<String, ActionConfig> getAllActionConfigs() {
Map<String, ActionConfig> retMap = new LinkedHashMap<String, ActionConfig>();
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<String, ResultConfig> getAllGlobalResults() {
Map<String, ResultConfig> retMap = new LinkedHashMap<String, ResultConfig>();
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<String, Object> getAllInterceptorConfigs() {
Map<String, Object> retMap = new LinkedHashMap<String, Object>();
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<String, ResultTypeConfig> getAllResultTypeConfigs() {
Map<String, ResultTypeConfig> retMap = new LinkedHashMap<String, ResultTypeConfig>();
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<ExceptionMappingConfig> getAllExceptionMappingConfigs() {
List<ExceptionMappingConfig> allExceptionMappings = new ArrayList<ExceptionMappingConfig>();
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.
* <p/>
* 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<String, ResultConfig> 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<String, Object> getInterceptorConfigs() {
return interceptorConfigs;
}
public String getName() {
return name;
}
public String getNamespace() {
return namespace;
}
public List<PackageConfig> getParents() {
return new ArrayList<PackageConfig>(parents);
}
/**
* gets the ResultTypeConfigs local to this package
*
* @return a Map of ResultTypeConfig objects keyed by result name
* @see ResultTypeConfig
*/
public Map<String, ResultTypeConfig> 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<ExceptionMappingConfig> 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<PackageConfig> 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<String, ResultConfig> resultConfigs) {
target.globalResultConfigs.putAll(resultConfigs);
return this;
}
public Builder addExceptionMappingConfig(ExceptionMappingConfig exceptionMappingConfig) {
target.globalExceptionMappingConfigs.add(exceptionMappingConfig);
return this;
}
public Builder addGlobalExceptionMappingConfigs(List<ExceptionMappingConfig> 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();
}
}
}
@@ -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;
/**
* <!-- START SNIPPET: javadoc -->
* <p/>
* Actions implementing Parameterizable will receive a map of the static parameters defined in the action
* configuration.
* <p/>
* <p/> The {@link com.opensymphony.xwork2.interceptor.StaticParametersInterceptor} must be in the action's interceptor
* queue for this to work.
* <p/>
* <!-- END SNIPPET: javadoc -->
*
* @author Jason Carreira
*/
public interface Parameterizable {
public void addParam(String name, String value);
void setParams(Map<String, String> params);
Map<String, String> getParams();
}
@@ -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.
* <p/>
* In the xml configuration file this is defined as the <code>result</code> tag.
*
* @author Mike
*/
public class ResultConfig extends Located implements Serializable {
private Map<String,String> params;
private String className;
private String name;
protected ResultConfig(String name, String className) {
this.name = name;
this.className = className;
params = new LinkedHashMap<String, String>();
}
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<String,String> 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<String,String> 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;
}
}
}
@@ -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.
* <p/>
* In the xml configuration file this is defined as the <code>result-type</code> 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<String,String> params;
protected ResultTypeConfig(String name, String className) {
this.name = name;
this.className = className;
params = new LinkedHashMap<String,String>();
}
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<String,String> 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<String,String> 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;
}
}
}
@@ -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;
}
}
@@ -0,0 +1,18 @@
<body>
<p>
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:
</p>
<pre>
PackageConfig config = new PackageConfig.Builder("myPackage").build();
</pre>
<p>
The methods on the builder object are chainable to support constructions like this:
</p>
<pre>
ResultConfig config = new ResultConfig.Builder("success", "myapp.MyResult")
.addParam("location", "/foo.jsp")
.build();
</pre>
</body>
@@ -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;
/**
* <p> 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<E> implements Serializable {
/**
* <p> The logging instance </p>
*/
private static final Logger log = LoggerFactory.getLogger(AbstractMatcher.class);
/**
* <p> Handles all wildcard pattern matching. </p>
*/
PatternMatcher<Object> wildcard;
/**
* <p> The compiled patterns and their associated target objects </p>
*/
List<Mapping<E>> compiledPatterns = new ArrayList<Mapping<E>>();;
public AbstractMatcher(PatternMatcher<?> helper) {
this.wildcard = (PatternMatcher<Object>) helper;
}
/**
* <p>
* 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.
* </p>
*
* <p>
* 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.
* </p>
*
* @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<E>(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<E>(name, pattern, target));
}
}
}
}
}
public void freeze() {
compiledPatterns = Collections.unmodifiableList(new ArrayList<Mapping<E>>());
}
/**
* <p> Matches the path against the compiled wildcard patterns. </p>
*
* @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<String,String> vars = new LinkedHashMap<String,String>();
for (Mapping<E> 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;
}
/**
* <p> Clones the target object and its children, replacing various
* properties with the values of the wildcard-matched strings. </p>
*
* @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<String, String> vars);
/**
* <p> Replaces parameter values
* </p>
*
* @param orig The original parameters with placehold values
* @param vars A Map of wildcard-matched strings
*/
protected Map<String,String> replaceParameters(Map<String, String> orig, Map<String,String> vars) {
Map<String,String> map = new LinkedHashMap<String,String>();
//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;
}
/**
* <p> 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.
* </p>
*
* @param val The value to convert
* @param vars A Map of wildcard-matched strings
* @return The new value
*/
protected String convertParam(String val, Map<String, String> vars) {
if (val == null) {
return null;
}
int len = val.length();
StringBuilder ret = new StringBuilder();
char c;
String varVal;
for (int x=0; x<len; x++) {
c = val.charAt(x);
if (x < len - 2 &&
c == '{' && '}' == val.charAt(x+2)) {
varVal = (String)vars.get(String.valueOf(val.charAt(x + 1)));
if (varVal != null) {
ret.append(varVal);
}
x += 2;
} else {
ret.append(c);
}
}
return ret.toString();
}
/**
* <p> Stores a compiled wildcard pattern and the object it came
* from. </p>
*/
private static class Mapping<E> implements Serializable {
/**
* <p> The original pattern. </p>
*/
private String original;
/**
* <p> The compiled pattern. </p>
*/
private Object pattern;
/**
* <p> The original object. </p>
*/
private E config;
/**
* <p> Contructs a read-only Mapping instance. </p>
*
* @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;
}
/**
* <p> Gets the compiled wildcard pattern. </p>
*
* @return The compiled pattern
*/
public Object getPattern() {
return this.pattern;
}
/**
* <p> Gets the object that contains the pattern. </p>
*
* @return The associated object
*/
public E getTarget() {
return this.config;
}
/**
* <p> Gets the original wildcard pattern. </p>
*
* @return The original pattern
*/
public String getOriginalPattern() {
return this.original;
}
}
}
@@ -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;
/**
* <p> 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<ActionConfig> implements Serializable {
/**
* <p> 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.</p>
*
* @param configs An array of ActionConfig's to process
* @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead
*/
@Deprecated public ActionConfigMatcher(Map<String, ActionConfig> configs) {
this(configs, false);
}
/**
* <p> 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. </p>
*
* <p>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.</p>
*
* @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<String, ActionConfig> configs,
boolean looseMatch) {
this(new WildcardHelper(), configs, looseMatch);
}
/**
* <p> 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. </p>
*
* <p>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.</p>
*
* @param configs An array of ActionConfig's to process
* @param looseMatch To loosely match wildcards or not
*/
public ActionConfigMatcher(PatternMatcher<?> patternMatcher,
Map<String, ActionConfig> configs,
boolean looseMatch) {
super(patternMatcher);
for (String name : configs.keySet()) {
addPattern(name, configs.get(name), looseMatch);
}
}
/**
* <p> Clones the ActionConfig and its children, replacing various
* properties with the values of the wildcard-matched strings. </p>
*
* @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<String, String> vars) {
String className = convertParam(orig.getClassName(), vars);
String methodName = convertParam(orig.getMethodName(), vars);
String pkgName = convertParam(orig.getPackageName(), vars);
Map<String,String> params = replaceParameters(orig.getParams(), vars);
Map<String,ResultConfig> results = new LinkedHashMap<String,ResultConfig>();
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<ExceptionMappingConfig> exs = new ArrayList<ExceptionMappingConfig>();
for (ExceptionMappingConfig ex : orig.getExceptionMappings()) {
String name = convertParam(ex.getName(), vars);
String exClassName = convertParam(ex.getExceptionClassName(), vars);
String exResult = convertParam(ex.getResult(), vars);
Map<String,String> 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();
}
}
@@ -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<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>();
protected RuntimeConfiguration runtimeConfiguration;
protected Container container;
protected String defaultFrameworkBeanName;
protected Set<String> loadedFileNames = new TreeSet<String>();
protected List<UnknownHandlerConfig> 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<UnknownHandlerConfig> getUnknownHandlerStack() {
return unknownHandlerStack;
}
public void setUnknownHandlerStack(List<UnknownHandlerConfig> unknownHandlerStack) {
this.unknownHandlerStack = unknownHandlerStack;
}
public Set<String> getPackageConfigNames() {
return packageContexts.keySet();
}
public Map<String, PackageConfig> getPackageConfigs() {
return packageContexts;
}
public Set<String> 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<ConfigurationProvider> providers) throws ConfigurationException {
// Silly copy necessary due to lack of ability to cast generic lists
List<ContainerProvider> contProviders = new ArrayList<ContainerProvider>();
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<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
packageContexts.clear();
loadedFileNames.clear();
List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();
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<Configuration>() {
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<String> 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.
*
* <p>
* 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<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>();
Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>();
for (PackageConfig packageConfig : packageContexts.values()) {
if (!packageConfig.isAbstract()) {
String namespace = packageConfig.getNamespace();
Map<String, ActionConfig> configs = namespaceActionConfigs.get(namespace);
if (configs == null) {
configs = new LinkedHashMap<String, ActionConfig>();
}
Map<String, ActionConfig> 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<String, ResultConfig> results, PackageConfig packageContext) {
String defaultResult = packageContext.getFullDefaultResultType();
for (Map.Entry<String, ResultConfig> 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<String, String> params = new TreeMap<String, String>(baseConfig.getParams());
Map<String, ResultConfig> results = new TreeMap<String, ResultConfig>();
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<InterceptorMapping> interceptors = new ArrayList<InterceptorMapping>(baseConfig.getInterceptors());
if (interceptors.size() <= 0) {
String defaultInterceptorRefName = packageContext.getFullDefaultInterceptorRef();
if (defaultInterceptorRefName != null) {
interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName,
new LinkedHashMap<String, String>(), 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<String, Map<String, ActionConfig>> namespaceActionConfigs;
private Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private NamespaceMatcher namespaceMatcher;
private Map<String, String> namespaceConfigs;
public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs, Map<String, String> namespaceConfigs) {
this.namespaceActionConfigs = namespaceActionConfigs;
this.namespaceConfigs = namespaceConfigs;
PatternMatcher<int[]> matcher = container.getInstance(PatternMatcher.class);
this.namespaceActionConfigMatchers = new LinkedHashMap<String, ActionConfigMatcher>();
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<String, ActionConfig> 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<String, Map<String, ActionConfig>> getActionConfigs() {
return namespaceActionConfigs;
}
@Override
public String toString() {
StringBuilder buff = new StringBuilder("RuntimeConfiguration - actions are\n");
for (String namespace : namespaceActionConfigs.keySet()) {
Map<String, ActionConfig> 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<String>(getProperty(key), getPropertyLocation(key)));
}
}
}
}
@@ -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<T> 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();
}
}
@@ -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<T> extends Located implements Factory<T> {
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<String, Object>() {
{
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();
}
}
@@ -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<String, PackageConfig> packages = new HashMap<String, PackageConfig>();
private Set<String> loadedFiles = new HashSet<String>();
private Container container;
protected List<UnknownHandlerConfig> 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<String> getPackageConfigNames() {
return packages.keySet();
}
public Map<String, PackageConfig> 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<ConfigurationProvider> providers) throws ConfigurationException {
throw new UnsupportedOperationException();
}
public PackageConfig removePackageConfig(String name) {
return packages.remove(name);
}
public Container getContainer() {
return container;
}
public Set<String> getLoadedFileNames() {
return loadedFiles;
}
public List<PackageProvider> reloadContainer(
List<ContainerProvider> containerProviders)
throws ConfigurationException {
throw new UnsupportedOperationException();
}
public List<UnknownHandlerConfig> getUnknownHandlerStack() {
return unknownHandlerStack;
}
public void setUnknownHandlerStack(List<UnknownHandlerConfig> unknownHandlerStack) {
this.unknownHandlerStack = unknownHandlerStack;
}
}
@@ -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<String,String> variables;
public NamespaceMatch(String pattern, Map<String, String> 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<String, String> getVariables() {
return variables;
}
}
@@ -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<NamespaceMatch> {
public NamespaceMatcher(PatternMatcher<?> patternMatcher,
Set<String> 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<String, String> vars) {
/*Map<String,String> origVars = (Map<String,String>)vars;
Map<String,String> map = new HashMap<String,String>();
for (Map.Entry<String,String> entry : origVars.entrySet()) {
if (entry.getKey().length() == 1) {
map.put("ns"+entry.getKey(), entry.getValue());
}
}
*/
return new NamespaceMatch(orig.getPattern(), vars);
}
}
@@ -0,0 +1 @@
<body>Configuration implementation classes.</body>
@@ -0,0 +1 @@
<body>Configuration core classes.</body>
@@ -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<InterceptorMapping> constructInterceptorReference(InterceptorLocator interceptorLocator,
String refName, Map<String,String> refParams, Location location, ObjectFactory objectFactory) throws ConfigurationException {
Object referencedConfig = interceptorLocator.getInterceptorConfig(refName);
List<InterceptorMapping> result = new ArrayList<InterceptorMapping>();
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<InterceptorMapping> constructParameterizedInterceptorReferences(
InterceptorLocator interceptorLocator, InterceptorStackConfig stackConfig, Map<String,String> refParams,
ObjectFactory objectFactory) {
List<InterceptorMapping> result;
Map<String, Map<String, String>> params = new LinkedHashMap<String, Map<String, String>>();
/*
* We strip
*
* <interceptor-ref name="someStack">
* <param name="interceptor1.param1">someValue</param>
* <param name="interceptor1.param2">anotherValue</param>
* </interceptor-ref>
*
* down to map
* interceptor1 -> [param1 -> someValue, param2 -> anotherValue]
*
* or
* <interceptor-ref name="someStack">
* <param name="interceptorStack1.interceptor1.param1">someValue</param>
* <param name="interceptorStack1.interceptor1.param2">anotherValue</param>
* </interceptor-ref>
*
* 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<String, String> map;
if (params.containsKey(name)) {
map = params.get(name);
} else {
map = new LinkedHashMap<String, String>();
}
map.put(key, value);
params.put(name, map);
} catch (Exception e) {
LOG.warn("No interceptor found for name = " + key);
}
}
result = new ArrayList<InterceptorMapping>(stackConfig.getInterceptors());
for (String key : params.keySet()) {
Map<String, String> map = params.get(key);
Object interceptorCfgObj = interceptorLocator.getInterceptorConfig(key);
/*
* Now we attempt to separate out param that refers to Interceptor
* and Interceptor stack, eg.
*
* <interceptor-ref name="someStack">
* <param name="interceptor1.param1">someValue</param>
* ...
* </interceptor-ref>
*
* vs
*
* <interceptor-ref name="someStack">
* <param name="interceptorStack1.interceptor1.param1">someValue</param>
* ...
* </interceptor-ref>
*/
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<InterceptorMapping> 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;
}
}
@@ -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());
}
}
@@ -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 <code>paramsElement</code> and return them as
* Map<String, String>. For example,
* <pre>
* <result ... >
* <param name="param1">value1</param>
* <param name="param2">value2</param>
* <param name="param3">value3</param>
* </result>
* </pre>
* will returns a Map<String, String> with the following key, value pairs :-
* <ul>
* <li>param1 - value1</li>
* <li>param2 - value2</li>
* <li>param3 - value3</li>
* </ul>
*
* @param paramsElement
* @return
*/
public static Map<String, String> getParams(Element paramsElement) {
LinkedHashMap<String, String> params = new LinkedHashMap<String, String>();
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 <code>element</code>.
* For example,
* <p/>
* <pre>
* <result>something_1</result>
* </pre>
* When the {@link org.w3c.dom.Element} <code>&lt;result&gt;</code> is passed in as
* argument (<code>element</code> to this method, it returns the content of it,
* namely, <code>something_1</code> 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;
}
}
}
@@ -0,0 +1 @@
<body>Configuration provider classes.</body>
@@ -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<String, Object> 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<String, Object> context, Object target, Object property);
}
@@ -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.
* <p/>
* 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);
}
@@ -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 <code>XWorkException</code> with no detail message.
*/
public TypeConversionException() {
}
/**
* Constructs a <code>XWorkException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public TypeConversionException(String s) {
super(s);
}
/**
* Constructs a <code>XWorkException</code> with no detail message.
*/
public TypeConversionException(Throwable cause) {
super(cause);
}
/**
* Constructs a <code>XWorkException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public TypeConversionException(String s, Throwable cause) {
super(s, cause);
}
}
@@ -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<String, Object> 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";
}
@@ -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;
/**
* <!-- START SNIPPET: description -->
* <p/>A marker annotation for type conversions at Type level.
* <!-- END SNIPPET: description -->
*
* <p/> <u>Annotation usage:</u>
*
* <!-- START SNIPPET: usage -->
* <p/>The Conversion annotation must be applied at Type level.
* <!-- END SNIPPET: usage -->
*
* <p/> <u>Annotation parameters:</u>
*
* <!-- START SNIPPET: parameters -->
* <table>
* <thead>
* <tr>
* <th>Parameter</th>
* <th>Required</th>
* <th>Default</th>
* <th>Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>conversion</td>
* <td>no</td>
* <td>&nbsp;</td>
* <td>used for Type Conversions applied at Type level.</td>
* </tr>
* </tbody>
* </table>
* <!-- END SNIPPET: parameters -->
*
* <p/> <u>Example code:</u>
*
* <pre>
* <!-- START SNIPPET: example -->
* &#64;Conversion()
* public class ConversionAction implements Action {
* }
*
* <!-- END SNIPPET: example -->
* </pre>
*
* @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 {};
}
@@ -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;
/**
* <code>ConversionRule</code>
*
* @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();
}
}
@@ -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;
/**
* <code>ConversionType</code>
*
* @author <a href="mailto:hermanns@aixcept.de">Rainer Hermanns</a>
* @version $Id$
*/
public enum ConversionType {
APPLICATION, CLASS;
@Override
public String toString() {
return super.toString().toUpperCase();
}
}
@@ -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;
/**
* <!-- START SNIPPET: description -->
* <p/>This annotation is used for class and application wide conversion rules.
* <p>
* Class wide conversion:<br/>
* The conversion rules will be assembled in a file called <code>XXXAction-conversion.properties</code>
* within the same package as the related action class.
* Set type to: <code>type = ConversionType.CLASS</code>
* </p>
* <p>
* Allication wide conversion:<br/>
* The conversion rules will be assembled within the <code>xwork-conversion.properties</code> file within the classpath root.
* Set type to: <code>type = ConversionType.APPLICATION</code>
* <p/>
* <!-- END SNIPPET: description -->
*
* <p/> <u>Annotation usage:</u>
*
* <!-- START SNIPPET: usage -->
* The TypeConversion annotation can be applied at property and method level.
* <!-- END SNIPPET: usage -->
*
* <p/> <u>Annotation parameters:</u>
*
* <!-- START SNIPPET: parameters -->
* <table>
* <thead>
* <tr>
* <th>Parameter</th>
* <th>Required</th>
* <th>Default</th>
* <th>Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>key</td>
* <td>no</td>
* <td>The annotated property/key name</td>
* <td>The optional property name mostly used within TYPE level annotations.</td>
* </tr>
* <tr>
* <td>type</td>
* <td>no</td>
* <td>ConversionType.CLASS</td>
* <td>Enum value of ConversionType. Determines whether the conversion should be applied at application or class level.</td>
* </tr>
* <tr>
* <td>rule</td>
* <td>no</td>
* <td>ConversionRule.PROPERTY</td>
* <td>Enum value of ConversionRule. The ConversionRule can be a property, a Collection or a Map.</td>
* </tr>
* <tr>
* <td>converter</td>
* <td>either this or value</td>
* <td>&nbsp;</td>
* <td>The class name of the TypeConverter to be used as converter.</td>
* </tr>
* <tr>
* <td>value</td>
* <td>either converter or this</td>
* <td>&nbsp;</td>
* <td>The value to set for ConversionRule.KEY_PROPERTY.</td>
* </tr>
* </tbody>
* </table>
*
* <!-- END SNIPPET: parameters -->
*
* <p/> <u>Example code:</u>
*
* <pre>
* <!-- START SNIPPET: example -->
* &#64;Conversion()
* public class ConversionAction implements Action {
*
* private String convertInt;
*
* private String convertDouble;
* private List users = null;
*
* private HashMap keyValues = null;
*
* &#64;TypeConversion(type = ConversionType.APPLICATION, converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
* public void setConvertInt( String convertInt ) {
* this.convertInt = convertInt;
* }
*
* &#64;TypeConversion(converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
* public void setConvertDouble( String convertDouble ) {
* this.convertDouble = convertDouble;
* }
*
* &#64;TypeConversion(rule = ConversionRule.COLLECTION, converter = "java.util.String")
* public void setUsers( List users ) {
* this.users = users;
* }
*
* &#64;TypeConversion(rule = ConversionRule.MAP, converter = "java.math.BigInteger")
* public void setKeyValues( HashMap keyValues ) {
* this.keyValues = keyValues;
* }
*
* &#64;TypeConversion(type = ConversionType.APPLICATION, property = "java.util.Date", converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
* public String execute() throws Exception {
* return SUCCESS;
* }
* }
* <!-- END SNIPPET: example -->
* </pre>
*
* @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 "";
}
@@ -0,0 +1 @@
<body>Type conversion annotations.</body>
@@ -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;
/**
* <!-- START SNIPPET: javadoc -->
* <p/>
* 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.
* <p/>
* <p/> 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 <b>ClassName-conversion.properties</b> (Note that the PointConverter should impl the TypeConverter
* interface):
* <p/>
* <p/><b>point = com.acme.PointConverter</b>
* <p/>
* <p/> 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.
* <p/>
* <p/> After this is done, you can now reference your point (using &lt;ww:property value="post"/&gt; 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.
* <p/>
* <p/> In some situations you may wish to apply a type converter globally. This can be done by editing the file
* <b>xwork-conversion.properties</b> 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:
* <p/>
* <p/><b>com.acme.Point = com.acme.PointConverter</b>
* <p/>
* <!-- END SNIPPET: javadoc -->
* <p/>
* <p/>
* <p/>
* <!-- START SNIPPET: i18n-note -->
* <p/>
* 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.
* <p/>
* <!-- END SNIPPET: i18n-note -->
* <p/>
* <p/>
* <p/>
* <!-- START SNIPPET: error-reporting -->
* <p/>
* 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.
* <p/>
* <p/> By default, all conversion errors are reported using the generic i18n key <b>xwork.default.invalid.fieldvalue</b>,
* which you can override (the default text is <i>Invalid field value for field "xxx"</i>, where xxx is the field name)
* in your global i18n resource bundle.
* <p/>
* <p/>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 <b>invalid.fieldvalue.xxx</b>, where xxx
* is the field name.
* <p/>
* <p/>It is important to know that none of these errors are actually reported directly. Rather, they are added to a map
* called <i>conversionErrors</i> in the ActionContext. There are several ways this map can then be accessed and the
* errors can be reported accordingly.
* <p/>
* <!-- END SNIPPET: error-reporting -->
*
* @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
* @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 {
}
@@ -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;
/**
* <!-- START SNIPPET: javadoc -->
*
* This {@link ObjectTypeDeterminer} looks at the <b>Class-conversion.properties</b> for entries that indicated what
* objects are contained within Maps and Collections. For Collections, such as Lists, the element is specified using the
* pattern <b>Element_xxx</b>, 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 <b>Key_xxx</b> and <b>Element_xxx</b>,
* respectively.
*
* <p/> From WebWork 2.1.x, the <b>Collection_xxx</b> format is still supported and honored, although it is deprecated
* and will be removed eventually.
*
* <!-- END SNIPPET: javadoc -->
*
*
* @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 <tt>true</tt>, if the collection or map is accessed via index, <tt>false</tt> otherwise.
* @return <tt>true</tt>, if the Collection or Map should be created, <tt>false</tt> 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 <T> 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 <code>null</code> if not found
*/
protected <T extends Annotation> T getAnnotation(Class parentClass, String property, Class<T> 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 <tt>null</tt> if none could be retrieved.
*/
private <T extends Annotation>T getAnnotationFromGetter(Class parentClass, String property, Class<T> 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 <tt>null</tt> if none could be retrieved.
*/
private <T extends Annotation>T getAnnotationFromSetter(Class parentClass, String property, Class<T> 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 <tt>true</tt> 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;
}
}
@@ -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<Class, Object> primitiveDefaults;
public DefaultTypeConverter() {
Map<Class, Object> map = new HashMap<Class, Object>();
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<String, Object> context, Object value, Class toType) {
return convertValue(value, toType);
}
public Object convertValue(Map<String, Object> context, Object target, Member member,
String propertyName, Object value, Class toType) {
return convertValue(context, value, toType);
}
public TypeConverter getTypeConverter( Map<String, Object> 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<Enum>)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);
}
}
@@ -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;
/**
* <code>EnumTypeConverter</code>
*
* <!-- START SNIPPET: description -->
* This class converts java 5 enums to String and from String[] to enum.
* <p/>
* 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}
* <p/>
* Java 5.0 now provides the following construct:
* {code}
* public static enum Season { WINTER, SPRING, SUMMER, FALL };
* {code}
* <!-- END SNIPPET: description -->
*
* <!-- START SNIPPET: example -->
* h3. Implementing Java 5 Enumeration Type Conversion
* <p/>
* 1. myAction-conversion.properties*
* <p/>
* Place a myAction-conversion.properties-file in the path of your Action.
* Add the following entry to the properties-file:
* {code}
* nameOfYourField=fullyClassifiedNameOfYourConverter
* {code}
* &nbsp;
* <p/>
* 2. myAction.java*
* Your action contains the _enumeration_:
* {code}
* public enum Criticality {DEBUG, INFO, WARNING, ERROR, FATAL}
* {code}
* &nbsp;
* * Your action contains the _private field_:
* {code}
* private myEnum myFieldForEnum;
* {code}
* &nbsp;
* Your action contains _getters and setters_ for your field:
* {code}
* public myEnum getCriticality() {
* return myFieldForEnum;
* }
*
* public void setCriticality(myEnum myFieldForEnum) {
* this.myFieldForEnum= myFieldForEnum;
* }
* {code}
* <p/>
* 3. JSP*
* <p/>
* &nbsp;&nbsp;&nbsp; In your jsp you can access an enumeration value just normal by using the known <ww:property>-Tag:
* {code}
* <ww:property value="myField"/>
* {code}
* <!-- END SNIPPET: example -->
*
* @author Tamara Cattivelli
* @author <a href="mailto:hermanns@aixcept.de">Rainer Hermanns</a>
* @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<String, Object> 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);
}
}
@@ -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 <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
*
* @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);
}
}
@@ -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.*;
/**
* <!-- START SNIPPET: javadoc -->
*
* 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.
*
* <p/> The following rules are used when handling null references:
*
* <ul>
*
* <li>If the property is declared <i>exactly</i> as a {@link Collection} or {@link List}, then an ArrayList shall be
* returned and assigned to the null references.</li>
*
* <li>If the property is declared as a {@link Map}, then a HashMap will be returned and assigned to the null
* references.</li>
*
* <li>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.</li>
*
* </ul>
*
* <!-- END SNIPPET: javadoc -->
*
* <!-- START SNIPPET: example -->
*
* For example, if a form element has a text field named <b>person.name</b> and the expression <i>person</i> evaluates
* to null, then this class will be invoked. Because the <i>person</i> expression evaluates to a <i>Person</i> 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.
*
* <!-- END SNIPPET: example>
*
* @author Matt Ho
* @author Patrick Lightbody
*/
public class InstantiatingNullHandler implements NullHandler {
/**
* @deprecated Use {@link ReflectionContextState#CREATE_NULL_OBJECTS} instead
*/
@Deprecated public static final String CREATE_NULL_OBJECTS = ReflectionContextState.CREATE_NULL_OBJECTS;
private static final Logger LOG = LoggerFactory.getLogger(InstantiatingNullHandler.class);
private ReflectionProvider reflectionProvider;
private ObjectFactory objectFactory;
private ObjectTypeDeterminer objectTypeDeterminer;
@Inject
public void setObjectTypeDeterminer(ObjectTypeDeterminer det) {
this.objectTypeDeterminer = det;
}
@Inject
public void setReflectionProvider(ReflectionProvider prov) {
this.reflectionProvider = prov;
}
@Inject
public void setObjectFactory(ObjectFactory fac) {
this.objectFactory = fac;
}
public Object nullMethodResult(Map<String, Object> context, Object target, String methodName, Object[] args) {
if (LOG.isDebugEnabled()) {
LOG.debug("Entering nullMethodResult ");
}
return null;
}
public Object nullPropertyValue(Map<String, Object> context, Object target, Object property) {
if (LOG.isDebugEnabled()) {
LOG.debug("Entering nullPropertyValue [target="+target+", property="+property+"]");
}
boolean c = ReflectionContextState.isCreatingNullObjects(context);
if (!c) {
return null;
}
if ((target == null) || (property == null)) {
return null;
}
try {
String propName = property.toString();
Object realTarget = reflectionProvider.getRealTarget(propName, context, target);
Class clazz = null;
if (realTarget != null) {
PropertyDescriptor pd = reflectionProvider.getPropertyDescriptor(realTarget.getClass(), propName);
if (pd == null) {
return null;
}
clazz = pd.getPropertyType();
}
if (clazz == null) {
// can't do much here!
return null;
}
Object param = createObject(clazz, realTarget, propName, context);
reflectionProvider.setValue(propName, context, realTarget, param);
return param;
} catch (Exception e) {
LOG.error("Could not create and/or set value back on to object", e);
}
return null;
}
private Object createObject(Class clazz, Object target, String property, Map<String, Object> context) throws Exception {
if (Collection.class.isAssignableFrom(clazz)) {
return new ArrayList();
} else if (clazz == Map.class) {
return new HashMap();
} else if (clazz == EnumMap.class) {
Class keyClass = objectTypeDeterminer.getKeyClass(target.getClass(), property);
return new EnumMap(keyClass);
}
return objectFactory.buildBean(clazz, context);
}
}
@@ -0,0 +1,583 @@
/*
* 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.ObjectFactory;
import com.opensymphony.xwork2.XWorkException;
import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
import com.opensymphony.xwork2.conversion.TypeConverter;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.XWorkList;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.*;
import java.util.*;
import org.apache.commons.lang.StringUtils;
/**
* <!-- START SNIPPET: javadoc -->
* <p/>
* 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:
* <p/>
* <ul>
* <li>String</li>
* <li>boolean / Boolean</li>
* <li>char / Character</li>
* <li>int / Integer, float / Float, long / Long, double / Double</li>
* <li>dates - uses the SHORT format for the Locale associated with the current request</li>
* <li>arrays - assuming the individual strings can be coverted to the individual items</li>
* <li>collections - if not object type can be determined, it is assumed to be a String and a new ArrayList is
* created</li>
* </ul>
* <p/> 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.
* <p/>
* <!-- END SNIPPET: javadoc -->
*
* @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
* @author Mike Mosiewicz
* @author Rainer Hermanns
* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
*/
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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> context, Object value) {
String result = null;
if (value instanceof int[]) {
int[] x = (int[]) value;
List<Integer> intArray = new ArrayList<Integer>(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<Long> longArray = new ArrayList<Long>(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<Double> doubleArray = new ArrayList<Double>(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<Boolean> booleanArray = new ArrayList<Boolean>(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;
}
}
@@ -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.
* <p/>
* <!-- START SNIPPET: javadoc -->
* <p/>
* 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.
* <p/>
* <p/> 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 <b>ClassName-conversion.properties</b> (Note that the PointConverter should impl the TypeConverter
* interface):
* <p/>
* <p/><b>point = com.acme.PointConverter</b>
* <p/>
* <p/> 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.
* <p/>
* <p/> After this is done, you can now reference your point (using &lt;s:property value="point"/&gt; 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.
* <p/>
* <p/> In some situations you may wish to apply a type converter globally. This can be done by editing the file
* <b>xwork-conversion.properties</b> 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:
* <p/>
* <p/><b>com.acme.Point = com.acme.PointConverter</b>
* <p/>
* <!-- END SNIPPET: javadoc -->
* <p/>
* <p/>
* <p/>
* <!-- START SNIPPET: i18n-note -->
* <p/>
* 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.
* <p/>
* <!-- END SNIPPET: i18n-note -->
* <p/>
* <p/>
* <p/>
* <!-- START SNIPPET: error-reporting -->
* <p/>
* 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.
* <p/>
* <p/> By default, all conversion errors are reported using the generic i18n key <b>xwork.default.invalid.fieldvalue</b>,
* which you can override (the default text is <i>Invalid field value for field "xxx"</i>, where xxx is the field name)
* in your global i18n resource bundle.
* <p/>
* <p/>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 <b>invalid.fieldvalue.xxx</b>, where xxx
* is the field name.
* <p/>
* <p/>It is important to know that none of these errors are actually reported directly. Rather, they are added to a map
* called <i>conversionErrors</i> in the ActionContext. There are several ways this map can then be accessed and the
* errors can be reported accordingly.
* <p/>
* <!-- END SNIPPET: error-reporting -->
*
* @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
* @author Rainer Hermanns
* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
* @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.
* <pre>
* Map<Class, Map<String, Object>>
* - Class -> convert to class
* - Map<String, Object>
* - 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
* </pre>
*/
protected HashMap<Class, Map<String, Object>> mappings = new HashMap<Class, Map<String, Object>>(); // action
/**
* Unavailable target class conversion mappings, serves as a simple cache.
*/
protected HashSet<Class> noMapping = new HashSet<Class>(); // action
/**
* Record class and its type converter mapping.
* <pre>
* - String - classname as String
* - TypeConverter - instance of TypeConverter
* </pre>
*/
protected HashMap<String, TypeConverter> defaultMappings = new HashMap<String, TypeConverter>(); // non-action (eg. returned value)
/**
* Record classes that doesn't have conversion mapping defined.
* <pre>
* - String -> classname as String
* </pre>
*/
protected HashSet<String> unknownMappings = new HashSet<String>(); // 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<String> 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<String> getIndexValues(String propertyName) {
Matcher matcher = messageIndexPattern.matcher(propertyName);
List<String> indexes = new ArrayList<String>();
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<String, Object> map, Object o, Class aClass) {
return convertValue(map, null, null, null, o, aClass);
}
/**
* Convert value from one form to another.
* Minimum requirement of arguments:
* <ul>
* <li>supplying context, toClass and value</li>
* <li>supplying context, target and value.</li>
* </ul>
*
* @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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> conversionErrors = (Map<String, Object>) context.get(ActionContext.CONVERSION_ERRORS);
if (conversionErrors == null) {
conversionErrors = new HashMap<String, Object>();
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<String, Object> 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<String, Object> 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<Object, Object> 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<String, Object> buildConverterMapping(Class clazz) throws Exception {
Map<String, Object> mapping = new HashMap<String, Object>();
// 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<String, Object> conditionalReload(Class clazz, Map<String, Object> oldValues) throws Exception {
Map<String, Object> 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<URL> 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;
}
}
@@ -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;
/**
* <code>ConversionDescription</code>
*
* @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());
}
/**
* <p>
* Sets the property name to be inserted into the related conversion.properties file.<br/>
* Note: Do not add COLLECTION_PREFIX or MAP_PREFIX keys to property names.
* </p>
*
* @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.
* <p>
* Example:<br/>
* property.name = converter.className<br/>
* Collection_property.name = converter.className<br/>
* Map_property.name = converter.className
* KeyProperty_name = id
* </p>
*
* @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 <code>conversion.properties</code> file name.
*
* @return fullQualifiedClassName
*/
public String getFullQualifiedClassName() {
return fullQualifiedClassName;
}
/**
* The fullQualifiedClassName attribute is used to create the special <code>conversion.properties</code> file name.
*
* @param fullQualifiedClassName
*/
public void setFullQualifiedClassName(String fullQualifiedClassName) {
this.fullQualifiedClassName = fullQualifiedClassName;
}
}
@@ -0,0 +1 @@
<body>Type conversion meta data classes.</body>
@@ -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> {
T currentReference;
boolean constructing;
List<DelegatingInvocationHandler<T>> 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<? super T> 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<T>>();
}
DelegatingInvocationHandler<T> invocationHandler =
new DelegatingInvocationHandler<T>();
invocationHandlers.add(invocationHandler);
return Proxy.newProxyInstance(
expectedType.getClassLoader(),
new Class[] { expectedType },
invocationHandler
);
}
void setProxyDelegates(T delegate) {
if (invocationHandlers != null) {
for (DelegatingInvocationHandler<T> invocationHandler
: invocationHandlers) {
invocationHandler.setDelegate(delegate);
}
}
}
static class DelegatingInvocationHandler<T> 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;
}
}
}
@@ -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.
*
* <p>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.
*
* <p>For example:
*
* <pre>
* class Foo {
*
* // Inject the int constant named "i".
* &#64;Inject("i") int i;
*
* // Inject the default implementation of Bar and the String constant
* // named "s".
* &#64;Inject Foo(Bar bar, @Inject("s") String s) {
* ...
* }
*
* // Inject the default implementation of Baz and the Bob implementation
* // named "foo".
* &#64;Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
* ...
* }
*
* // Inject the default implementation of Tee.
* &#64;Inject void setTee(Tee tee) {
* ...
* }
* }
* </pre>
*
* <p>To create and inject an instance of {@code Foo}:
*
* <pre>
* Container c = ...;
* Foo foo = c.inject(Foo.class);
* </pre>
*
* @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> T inject(Class<T> implementation);
/**
* Gets an instance of the given dependency which was declared in
* {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
*/
<T> T getInstance(Class<T> type, String name);
/**
* Convenience method.&nbsp;Equivalent to {@code getInstance(type,
* DEFAULT_NAME)}.
*/
<T> T getInstance(Class<T> type);
/**
* Gets a set of all registered names for the given type
* @param type The instance type
* @return A set of registered names
*/
Set<String> 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();
}
@@ -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.
*
* <p>Adds the following factories by default:
*
* <ul>
* <li>Injects the current {@link Container}.
* <li>Injects the {@link Logger} for the injected member's declaring class.
* </ul>
*
* @author crazybob@google.com (Bob Lee)
*/
public final class ContainerBuilder {
final Map<Key<?>, InternalFactory<?>> factories =
new HashMap<Key<?>, InternalFactory<?>>();
final List<InternalFactory<?>> singletonFactories =
new ArrayList<InternalFactory<?>>();
final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
boolean created;
boolean allowDuplicates = false;
private static final InternalFactory<Container> CONTAINER_FACTORY =
new InternalFactory<Container>() {
public Container create(InternalContext context) {
return context.getContainer();
}
};
private static final InternalFactory<Logger> LOGGER_FACTORY =
new InternalFactory<Logger>() {
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 <T> ContainerBuilder factory(final Key<T> key,
InternalFactory<? extends T> factory, Scope scope) {
ensureNotCreated();
checkKey(key);
final InternalFactory<? extends T> scopedFactory =
scope.scopeFactory(key.getType(), key.getName(), factory);
factories.put(key, scopedFactory);
if (scope == Scope.SINGLETON) {
singletonFactories.add(new InternalFactory<T>() {
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 <T> ContainerBuilder factory(final Class<T> type, final String name,
final Factory<? extends T> factory, Scope scope) {
InternalFactory<T> internalFactory =
new InternalFactory<T>() {
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<String, Object>() {{
put("type", type);
put("name", name);
put("factory", factory);
}}.toString();
}
};
return factory(Key.newInstance(type, name), internalFactory, scope);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, factory, scope)}.
*
* @see #factory(Class, String, Factory, Scope)
*/
public <T> ContainerBuilder factory(Class<T> type,
Factory<? extends T> factory, Scope scope) {
return factory(type, Container.DEFAULT_NAME, factory, scope);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type, name, factory,
* Scope.DEFAULT)}.
*
* @see #factory(Class, String, Factory, Scope)
*/
public <T> ContainerBuilder factory(Class<T> type, String name,
Factory<? extends T> factory) {
return factory(type, name, factory, Scope.DEFAULT);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, factory, Scope.DEFAULT)}.
*
* @see #factory(Class, String, Factory, Scope)
*/
public <T> ContainerBuilder factory(Class<T> type,
Factory<? extends T> 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 <T> ContainerBuilder factory(final Class<T> type, final String name,
final Class<? extends T> 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<? extends T> factory = new InternalFactory<T>() {
volatile ContainerImpl.ConstructorInjector<? extends T> 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<String, Object>() {{
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.
*
* <p>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 <T> ContainerBuilder factory(final Class<T> type, String name,
final Class<? extends T> implementation) {
Scoped scoped = implementation.getAnnotation(Scoped.class);
Scope scope = scoped == null ? Scope.DEFAULT : scoped.value();
return factory(type, name, implementation, scope);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, implementation)}.
*
* @see #factory(Class, String, Class)
*/
public <T> ContainerBuilder factory(Class<T> type,
Class<? extends T> implementation) {
return factory(type, Container.DEFAULT_NAME, implementation);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, type)}.
*
* @see #factory(Class, String, Class)
*/
public <T> ContainerBuilder factory(Class<T> type) {
return factory(type, Container.DEFAULT_NAME, type);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type, name, type)}.
*
* @see #factory(Class, String, Class)
*/
public <T> ContainerBuilder factory(Class<T> type, String name) {
return factory(type, name, type);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, implementation, scope)}.
*
* @see #factory(Class, String, Class, Scope)
*/
public <T> ContainerBuilder factory(Class<T> type,
Class<? extends T> implementation, Scope scope) {
return factory(type, Container.DEFAULT_NAME, implementation, scope);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, type, scope)}.
*
* @see #factory(Class, String, Class, Scope)
*/
public <T> ContainerBuilder factory(Class<T> type, Scope scope) {
return factory(type, Container.DEFAULT_NAME, type, scope);
}
/**
* Convenience method.&nbsp;Equivalent to {@code factory(type, name, type,
* scope)}.
*
* @see #factory(Class, String, Class, Scope)
*/
public <T> ContainerBuilder factory(Class<T> type, String name, Scope scope) {
return factory(type, name, type, scope);
}
/**
* Convenience method.&nbsp;Equivalent to {@code alias(type, Container.DEFAULT_NAME,
* type)}.
*
* @see #alias(Class, String, String)
*/
public <T> ContainerBuilder alias(Class<T> 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 <T> ContainerBuilder alias(Class<T> 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 <T> ContainerBuilder alias(final Key<T> key,
final Key<T> aliasKey) {
ensureNotCreated();
checkKey(aliasKey);
final InternalFactory<? extends T> scopedFactory =
(InternalFactory<? extends T>)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 <E extends Enum<E>> ContainerBuilder constant(String name, E value) {
return constant(value.getDeclaringClass(), name, value);
}
/**
* Maps a constant value to the given type and name.
*/
private <T> ContainerBuilder constant(final Class<T> type, final String name,
final T value) {
InternalFactory<T> factory = new InternalFactory<T>() {
public T create(InternalContext ignored) {
return value;
}
@Override
public String toString() {
return new LinkedHashMap<String, Object>() {
{
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.&nbsp;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<Key<?>, InternalFactory<?>>(factories));
if (loadSingletons) {
container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
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);
}
}
@@ -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<Key<?>, InternalFactory<?>> factories;
final Map<Class<?>,Set<String>> factoryNamesByType;
ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {
this.factories = factories;
Map<Class<?>,Set<String>> map = new HashMap<Class<?>,Set<String>>();
for (Key<?> key : factories.keySet()) {
Set<String> names = map.get(key.getType());
if (names == null) {
names = new HashSet<String>();
map.put(key.getType(), names);
}
names.add(key.getName());
}
for (Entry<Class<?>,Set<String>> entry : map.entrySet()) {
entry.setValue(Collections.unmodifiableSet(entry.getValue()));
}
this.factoryNamesByType = Collections.unmodifiableMap(map);
}
@SuppressWarnings("unchecked")
<T> InternalFactory<? extends T> getFactory(Key<T> key) {
return (InternalFactory<T>) factories.get(key);
}
/**
* Field and method injectors.
*/
final Map<Class<?>, List<Injector>> injectors =
new ReferenceCache<Class<?>, List<Injector>>() {
@Override
protected List<Injector> create(Class<?> key) {
List<Injector> injectors = new ArrayList<Injector>();
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<Injector> 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<Class<?>> staticInjections) {
final List<Injector> injectors = new ArrayList<Injector>();
for (Class<?> clazz : staticInjections) {
addInjectorsForFields(clazz.getDeclaredFields(), true, injectors);
addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors);
}
callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
for (Injector injector : injectors) {
injector.inject(context, null);
}
return null;
}
});
}
void addInjectorsForMethods(Method[] methods, boolean statics,
List<Injector> injectors) {
addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
new InjectorFactory<Method>() {
public Injector create(ContainerImpl container, Method method,
String name) throws MissingDependencyException {
return new MethodInjector(container, method, name);
}
});
}
void addInjectorsForFields(Field[] fields, boolean statics,
List<Injector> injectors) {
addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
new InjectorFactory<Field>() {
public Injector create(ContainerImpl container, Field field,
String name) throws MissingDependencyException {
return new FieldInjector(container, field, name);
}
});
}
<M extends Member & AnnotatedElement> void addInjectorsForMembers(
List<M> members, boolean statics, List<Injector> injectors,
InjectorFactory<M> 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<M extends Member & AnnotatedElement> {
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
*/
<M extends AccessibleObject & Member> ParameterInjector<?>[]
getParametersInjectors(M member,
Annotation[][] annotations, Class[] parameterTypes, String defaultName)
throws MissingDependencyException {
List<ParameterInjector<?>> parameterInjectors =
new ArrayList<ParameterInjector<?>>();
Iterator<Annotation[]> 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);
}
<T> ParameterInjector<T> createParameterInjector(
Key<T> key, Member member) throws MissingDependencyException {
InternalFactory<? extends T> factory = getFactory(key);
if (factory == null) {
throw new MissingDependencyException(
"No mapping found for dependency " + key + " in " + member + ".");
}
ExternalContext<T> externalContext =
ExternalContext.newInstance(member, key, this);
return new ParameterInjector<T>(externalContext, factory);
}
@SuppressWarnings("unchecked")
private ParameterInjector<?>[] toArray(
List<ParameterInjector<?>> 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<Class<?>, ConstructorInjector> constructors =
new ReferenceCache<Class<?>, ConstructorInjector>() {
@Override
@SuppressWarnings("unchecked")
protected ConstructorInjector<?> create(Class<?> implementation) {
return new ConstructorInjector(ContainerImpl.this, implementation);
}
};
static class ConstructorInjector<T> {
final Class<T> implementation;
final List<Injector> injectors;
final Constructor<T> constructor;
final ParameterInjector<?>[] parameterInjectors;
ConstructorInjector(ContainerImpl container, Class<T> 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<T> constructor) throws MissingDependencyException{
return constructor.getParameterTypes().length == 0
? null // default constructor.
: container.getParametersInjectors(
constructor,
constructor.getParameterAnnotations(),
constructor.getParameterTypes(),
inject.value()
);
}
@SuppressWarnings("unchecked")
private Constructor<T> findConstructorIn(Class<T> implementation) {
Constructor<T> found = null;
Constructor<T>[] declaredConstructors = (Constructor<T>[]) implementation
.getDeclaredConstructors();
for(Constructor<T> 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<? super T> expectedType) {
ConstructionContext<T> 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<T> {
final ExternalContext<T> externalContext;
final InternalFactory<? extends T> factory;
public ParameterInjector(ExternalContext<T> externalContext,
InternalFactory<? extends T> 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<Injector> injectors = this.injectors.get(o.getClass());
for (Injector injector : injectors) {
injector.inject(context, o);
}
}
<T> T inject(Class<T> implementation, InternalContext context) {
try {
ConstructorInjector<T> constructor = getConstructor(implementation);
return implementation.cast(
constructor.construct(context, implementation));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
<T> T getInstance(Class<T> type, String name, InternalContext context) {
ExternalContext<?> previous = context.getExternalContext();
Key<T> 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> T getInstance(Class<T> type, InternalContext context) {
return getInstance(type, DEFAULT_NAME, context);
}
public void inject(final Object o) {
callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
inject(o, context);
return null;
}
});
}
public <T> T inject(final Class<T> implementation) {
return callInContext(new ContextualCallable<T>() {
public T call(InternalContext context) {
return inject(implementation, context);
}
});
}
public <T> T getInstance(final Class<T> type, final String name) {
return callInContext(new ContextualCallable<T>() {
public T call(InternalContext context) {
return getInstance(type, name, context);
}
});
}
public <T> T getInstance(final Class<T> type) {
return callInContext(new ContextualCallable<T>() {
public T call(InternalContext context) {
return getInstance(type, context);
}
});
}
public Set<String> getInstanceNames(final Class<?> type) {
return factoryNamesByType.get(type);
}
ThreadLocal<Object[]> localContext =
new ThreadLocal<Object[]>() {
@Override
protected Object[] initialValue() {
return new Object[1];
}
};
/**
* Looks up thread local context. Creates (and removes) a new context if
* necessary.
*/
<T> T callInContext(ContextualCallable<T> 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> {
T call(InternalContext context);
}
/**
* Gets a constructor function for a given implementation class.
*/
@SuppressWarnings("unchecked")
<T> ConstructorInjector<T> getConstructor(Class<T> implementation) {
return constructors.get(implementation);
}
final ThreadLocal<Object> localScopeStrategy =
new ThreadLocal<Object>();
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);
}
}
}
@@ -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();
}
@@ -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);
}
}
@@ -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<T> implements Context {
final Member member;
final Key<T> key;
final ContainerImpl container;
public ExternalContext(Member member, Key<T> key, ContainerImpl container) {
this.member = member;
this.key = key;
this.container = container;
}
public Class<T> 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<String, Object>() {{
put("member", member);
put("type", getType());
put("name", getName());
put("container", container);
}}.toString();
}
static <T> ExternalContext<T> newInstance(Member member, Key<T> key,
ContainerImpl container) {
return new ExternalContext<T>(member, key, container);
}
}

Some files were not shown because too many files have changed in this diff Show More