As per the discussion late last week, we have a patch for Xwork so that it can use an external container to resolve component references.
I've created the following issue - http://jira.opensymphony.com/secure/ViewIssue.jspa?key=XW-122
I've added a breakdown of the changes below, in summary, this is how it works-
You can configure a action to have external references in the xwork.xml using a new <external-ref> tag on the action
When the action is configured the external refs are stored on the action config.
When the action is invoked, there is a new interceptor that will resolve these references. It does this by using a new attribute on the package config called externalReferenceResolver i.e.
<package name="default" externalReferenceResolver="com.atlassian.xwork.ext.SpringServletContextReferenceResolver">
In this case, the SpringServletContextReferenceResolver implementation will handle the work of looking up and setting the references on the action.
Note, if a resolver is not found on the actions package, it will tranverse up the package heirarchy to find one.
We have an implementation for Spring, but I have not included it in this patch as it should probably go into an xwork-ext sub-project. Let me know what you want me to do with this...
Here are the changes and additions to the xwork codebase -
1. added 2 new config attributes -
- a new element external-ref to the action element in the config.
i.e <external-ref name="foo">Foo</external-ref>
where name is the setter method name and Foo is the reference to lookup.
- added an attribute to the package element called externalReferenceResolver which supplies a FQ classname to an ExternalReferenceResolver implementation.
2. Updated the xwork DTD accordingly.
3. Added 4 new classes -
- External Reference - an encapsulation of the external-ref tag
- ExternalReferenceResolver - an interface to provide implementations for resolving references from an external container
- ExternalReferencesInterceptor - will resolve references on a given ActionInvocation
- ReferenceResolverException - thrown by ExternalReferenceResolver
4. Added support for external references to the ActionConfig. I also added the attribute packageName to the ActionConfig, so that the Interceptor could determine which package the action belonged to in order to find the externalReferenceResolver.
5. Added support for the externalReferenceResolver attribute to the PackageConfig.
6. Added support for the extra configuration to the XMLConfigurationProvider and DefaultConfigurationProvider
7. Added tests in the org.opensymphony.xwork.config.ExternalReferenceResolverTest
I've attached a cvs patch with all the changes. I built the patch against the latest src.
Cheers,
Ross
Index: project.properties =================================================================== RCS file: /cvs/xwork/project.properties,v retrieving revision 1.1 diff -u -r1.1 project.properties --- project.properties 25 Jul 2003 06:53:27 -0000 1.1 +++ project.properties 17 Nov 2003 23:09:31 -0000 @@ -12,3 +12,5 @@ maven.xdoc.version=${pom.currentVersion} xwork.jar.dir = /vol0/sites/opensymphony.indigoegg.com/maven/xwork/jars + +maven.junit.fork=true \ No newline at end of file Index: project.xml =================================================================== RCS file: /cvs/xwork/project.xml,v retrieving revision 1.3 diff -u -r1.3 project.xml --- project.xml 2 Aug 2003 08:45:02 -0000 1.3 +++ project.xml 17 Nov 2003 23:09:33 -0000 @@ -115,7 +115,7 @@ </dependency> <dependency> <id>ognl</id> - <version>2.5.1</version> + <version>2.6.3</version> <properties> <war.bundle.jar>true</war.bundle.jar> </properties> @@ -135,7 +135,20 @@ <war.bundle.jar>true</war.bundle.jar> </properties> </dependency> - + <dependency> + <id>commons-beanutils</id> + <version>1.6.1</version> + <properties> + <war.bundle.jar>true</war.bundle.jar> + </properties> + </dependency> + <dependency> + <id>commons-collections</id> + <version>2.1</version> + <properties> + <war.bundle.jar>true</war.bundle.jar> + </properties> + </dependency> <dependency> <id>junit</id> <version>3.8.1</version> Index: src/java/com/opensymphony/xwork/config/ExternalReferenceResolver.java =================================================================== RCS file: src/java/com/opensymphony/xwork/config/ExternalReferenceResolver.java diff -N src/java/com/opensymphony/xwork/config/ExternalReferenceResolver.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/com/opensymphony/xwork/config/ExternalReferenceResolver.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,17 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork.config; + +import com.opensymphony.xwork.ActionInvocation; + +/** + * Resolves references declared in the action configuration from an external source + */ +public interface ExternalReferenceResolver { + + public void resolveReferences(ActionInvocation invocation) throws ReferenceResolverException; +} Index: src/java/com/opensymphony/xwork/config/ReferenceResolverException.java =================================================================== RCS file: src/java/com/opensymphony/xwork/config/ReferenceResolverException.java diff -N src/java/com/opensymphony/xwork/config/ReferenceResolverException.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/com/opensymphony/xwork/config/ReferenceResolverException.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,49 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork.config; + +import com.opensymphony.xwork.XworkException; + +/** + * @author Mike + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class ReferenceResolverException extends XworkException { + + + /** + * + */ + public ReferenceResolverException() { + super(); + } + + /** + * @param s + */ + public ReferenceResolverException(String s) { + super(s); + } + + /** + * @param s + * @param cause + */ + public ReferenceResolverException(String s, Throwable cause) { + super(s, cause); + } + + /** + * @param cause + */ + public ReferenceResolverException(Throwable cause) { + super(cause); + } + +} Index: src/java/com/opensymphony/xwork/config/entities/ExternalReference.java =================================================================== RCS file: src/java/com/opensymphony/xwork/config/entities/ExternalReference.java diff -N src/java/com/opensymphony/xwork/config/entities/ExternalReference.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/com/opensymphony/xwork/config/entities/ExternalReference.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,78 @@ +/* + * Created on Nov 12, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork.config.entities; + +/** + * @author Ross + * + * Encapsulates an external reference in the xwork configuration + */ +public class ExternalReference { + + private String name; + private String externalRef; + private boolean required = true; + + /** + * default constructor + */ + public ExternalReference(){} + + /** + * @param name the name of the attribute the external reference refers to + * @param externalRef the name used to query the external source + * @param required determines whether an exception should be thrown if the reference is not resolved + */ + public ExternalReference(String name, String externalRef, boolean required) + { + this.name = name; + this.externalRef = externalRef; + this.required = required; + } + /** + * @return Returns the externalRef. + */ + public String getExternalRef() { + return externalRef; + } + + /** + * @param externalRef The externalRef to set. + */ + public void setExternalRef(String externalRef) { + this.externalRef = externalRef; + } + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return Returns the required. + */ + public boolean isRequired() { + return required; + } + + /** + * @param required The required to set. + */ + public void setRequired(boolean required) { + this.required = required; + } + +} Index: src/java/com/opensymphony/xwork/interceptor/ExternalReferencesInterceptor.java =================================================================== RCS file: src/java/com/opensymphony/xwork/interceptor/ExternalReferencesInterceptor.java diff -N src/java/com/opensymphony/xwork/interceptor/ExternalReferencesInterceptor.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/com/opensymphony/xwork/interceptor/ExternalReferencesInterceptor.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,42 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork.interceptor; + +import com.opensymphony.xwork.ActionInvocation; +import com.opensymphony.xwork.config.ConfigurationManager; +import com.opensymphony.xwork.config.ExternalReferenceResolver; +import com.opensymphony.xwork.config.entities.PackageConfig; + +/** + * @author Ross + * + * Resolves external references using the <code>ExternalReferenceResolver</code> configured on the package + * Reference Resolution is encapsulated in an interceptor so that the user can configure when references should + * be resloved + */ +public class ExternalReferencesInterceptor extends AroundInterceptor { + //~ Methods //////////////////////////////////////////////////////////////// + + protected void after(ActionInvocation dispatcher, String result) throws Exception { + } + + protected void before(ActionInvocation invocation) throws Exception + { + String packageName = invocation.getProxy().getConfig().getPackageName(); + PackageConfig packageConfig = ConfigurationManager.getConfiguration().getPackageConfig(packageName); + + if(packageConfig!=null) + { + ExternalReferenceResolver erResolver = packageConfig.getExternalRefResolver(); + + if(erResolver!=null) + { + erResolver.resolveReferences(invocation); + } + } + } +} Index: src/test/com/opensymphony/xwork/ExternalReferenceAction.java =================================================================== RCS file: src/test/com/opensymphony/xwork/ExternalReferenceAction.java diff -N src/test/com/opensymphony/xwork/ExternalReferenceAction.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/test/com/opensymphony/xwork/ExternalReferenceAction.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,38 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to Window - Preferences - + * Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork; + +/** + * @author Mike + * + * To change the template for this generated type comment go to Window - + * Preferences - Java - Code Generation - Code and Comments + */ +public class ExternalReferenceAction implements Action { + + private Foo foo; + + public String execute() throws Exception { + return SUCCESS; + } + + /** + * @return Returns the foo. + */ + public Foo getFoo() { + return foo; + } + + /** + * @param foo + * The foo to set. + */ + public void setFoo(Foo foo) { + this.foo = foo; + } + +} Index: src/test/com/opensymphony/xwork/Foo.java =================================================================== RCS file: src/test/com/opensymphony/xwork/Foo.java diff -N src/test/com/opensymphony/xwork/Foo.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/test/com/opensymphony/xwork/Foo.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,30 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork; + +/** + * @author Mike + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class Foo { + + String name = null; + + public Foo() { + name = "not set"; + } + + public Foo(String name) { + this.name = name; + } + public String getName() + { + return name; + } +} Index: src/test/com/opensymphony/xwork/config/ExternalReferenceResolverTest.java =================================================================== RCS file: src/test/com/opensymphony/xwork/config/ExternalReferenceResolverTest.java diff -N src/test/com/opensymphony/xwork/config/ExternalReferenceResolverTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/test/com/opensymphony/xwork/config/ExternalReferenceResolverTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,124 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork.config; + +import com.opensymphony.xwork.Action; +import com.opensymphony.xwork.ActionProxy; +import com.opensymphony.xwork.ActionProxyFactory; +import com.opensymphony.xwork.ExternalReferenceAction; +import com.opensymphony.xwork.config.entities.PackageConfig; +import com.opensymphony.xwork.config.providers.XmlConfigurationProvider; + +import junit.framework.TestCase; + +/** + * @author Ross + * + * Test support for external-ref tag in xwork.xml. This tag allows objects from 'external' sources + * to be used by an action + */ +public class ExternalReferenceResolverTest extends TestCase { + + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + XmlConfigurationProvider c = new XmlConfigurationProvider(); + ConfigurationManager.addConfigurationProvider(c); + ConfigurationManager.getConfiguration().reload(); + } + + /** + * test that resolver has been loaded and given to the package config + */ + public void testResolverIsInstanciated() throws Exception + { + RuntimeConfiguration config = ConfigurationManager.getConfiguration().getRuntimeConfiguration(); + PackageConfig packageConfig = ConfigurationManager.getConfiguration().getPackageConfig("default"); + + assertNotNull("There should be a package called 'default'", packageConfig); + + ExternalReferenceResolver err = packageConfig.getExternalRefResolver(); + assertNotNull(err); + assertTrue(err instanceof TestExternalReferenceResolver); + } + + /** + * Test that the ActionInvocation implementation uses the resolver to resolve + * external references + * @throws Exception because it wants to! + */ + public void testResolverResolvesDependancies() throws Exception + { + ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(null, "TestExternalRefResolver", null); + Action action = proxy.getAction(); + assertNotNull("Action should be null", action); + assertTrue("Action should be an ExternalReferenceAction", action instanceof ExternalReferenceAction); + + ExternalReferenceAction erAction = (ExternalReferenceAction)action; + assertNull("The Foo object should not have been resolved yet", erAction.getFoo()); + + proxy.getInvocation().invoke(); + + assertNotNull("The Foo object should have been resolved", erAction.getFoo()); + assertEquals("Foos name should be 'Little Foo'", "Little Foo", erAction.getFoo().getName()); + } + + /** + * Test that required dependacies cause exception when not found and non-dependant do not + * TestExternalRefResolver2 has two external-refs, one of which doesn't exist but is also not required + * @throws Exception + */ + public void testResolverRespectsRequiredDependancies() throws Exception + { + ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(null, "TestExternalRefResolver2", null); + Action action = proxy.getAction(); + assertNotNull("Action should be null", action); + assertTrue("Action should be an ExternalReferenceAction", action instanceof ExternalReferenceAction); + + ExternalReferenceAction erAction = (ExternalReferenceAction)action; + assertNull("The Foo object should not have been resolved yet", erAction.getFoo()); + + proxy.getInvocation().invoke(); + + assertNotNull("The Foo object should have been resolved", erAction.getFoo()); + assertEquals("Foos name should be 'Little Foo'", "Little Foo", erAction.getFoo().getName()); + + //now test that a required dependacy that is missing will throw an exception + proxy = ActionProxyFactory.getFactory().createActionProxy(null, "TestExternalRefResolver3", null); + action = proxy.getAction(); + assertNotNull("Action should be null", action); + erAction = (ExternalReferenceAction)action; + + try { + proxy.getInvocation().invoke(); + fail("Invoking the action should have thrown ReferenceResolverException"); + } catch (ReferenceResolverException e) { + // expected + } + } + + + /** + * The TestExternalRefResolver5 is defined in a child package which doesn't have an external + * reference resolver defined on it, so the resolver should be used from its parent + * @throws Exception + */ + public void testResolverOnParentPackage() throws Exception + { + ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy( + "test/externalRef/","TestExternalRefResolver4", null); + + ExternalReferenceAction erAction = (ExternalReferenceAction) proxy.getAction(); + + proxy.getInvocation().invoke(); + + assertNotNull("The Foo object should have been resolved", erAction.getFoo()); + assertEquals("Foos name should be 'Little Foo'", "Little Foo", erAction.getFoo().getName()); + + } +} Index: src/test/com/opensymphony/xwork/config/TestExternalReferenceResolver.java =================================================================== RCS file: src/test/com/opensymphony/xwork/config/TestExternalReferenceResolver.java diff -N src/test/com/opensymphony/xwork/config/TestExternalReferenceResolver.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/test/com/opensymphony/xwork/config/TestExternalReferenceResolver.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,78 @@ +/* + * Created on Nov 11, 2003 + * + * To change the template for this generated file go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +package com.opensymphony.xwork.config; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.beanutils.BeanUtils; + +import com.opensymphony.xwork.ActionInvocation; +import com.opensymphony.xwork.Foo; +import com.opensymphony.xwork.config.entities.ExternalReference; + + +/** + * Test resolver + */ +public class TestExternalReferenceResolver implements ExternalReferenceResolver +{ + private Map references; + + public TestExternalReferenceResolver() + { + references = new HashMap(); + references.put("entity1", "I am entity 1"); + references.put("entity2", "I am entity 2"); + references.put("myFoo", new Foo("Little Foo")); + } + + /* (non-Javadoc) + * @see com.opensymphony.xwork.config.ExternalReferenceResolver#resolveReference(java.lang.String) + */ + public void resolveReferences(ActionInvocation invocation) throws ReferenceResolverException { + List refs = invocation.getProxy().getConfig().getExternalRefs(); + + ExternalReference reference; + String method; + for(Iterator iter = refs.iterator(); iter.hasNext(); ) + { + reference = (ExternalReference)iter.next(); + Object obj = null; + + try { + obj = getReference(reference.getExternalRef()); + } catch (IllegalArgumentException e1) { + if(reference.isRequired()) { + //if a dependacy is required but wasn't found throw an exception + throw new ReferenceResolverException("Could not resolve external references using key: " + reference.getExternalRef()); + } else { + return; + } + } + + try { + BeanUtils.setProperty(invocation.getAction(), reference.getName(), obj); + } catch (Exception e) { + throw new ReferenceResolverException("Failed to set external reference: " + + reference.getExternalRef() + " for bean attribute: " + reference.getName() + ". " + + e.getMessage(), e ); + } + } + } + + private Object getReference(Object key) throws IllegalArgumentException + { + Object result = references.get(key); + if(result==null) throw new IllegalArgumentException("Object was not found for key: " + key); + + return result; + } + +}