Commits

rebelutionary  committed 18bc37c

Adding preliminary external reference resolver support as built by Ross (ross@atlassian.com).

git-svn-id: http://svn.opensymphony.com/svn/xwork/trunk@127e221344d-f017-0410-9bd5-d282ab1896d7

  • Participants
  • Parent commits 18cbdbc

Comments (0)

Files changed (18)

File project.properties

 maven.xdoc.version=${pom.currentVersion}
 
 xwork.jar.dir = /vol0/sites/opensymphony.indigoegg.com/maven/xwork/jars
+
+maven.junit.fork=true
         </dependency>
         <dependency>
             <id>ognl</id>
-            <version>2.5.1</version>
+            <version>2.6.3</version>
             <properties>
                 <war.bundle.jar>true</war.bundle.jar>
             </properties>
                 <war.bundle.jar>true</war.bundle.jar>
             </properties>
         </dependency>
-
         <dependency>
             <id>junit</id>
             <version>3.8.1</version>

File src/etc/xwork-1.0.dtd

     extends CDATA #IMPLIED
     namespace CDATA #IMPLIED
     abstract CDATA #IMPLIED
+    externalReferenceResolver NMTOKEN #IMPLIED
 >
 
 <!ELEMENT result-types (result-type+)>
     name CDATA #REQUIRED
 >
 
+<!ELEMENT external-ref (#PCDATA)>
+<!ATTLIST external-ref
+    name NMTOKEN #REQUIRED
+    required (true|false) "false"
+>
+
 <!ELEMENT global-results (result+)>
 
-<!ELEMENT action (param|result|interceptor-ref)*>
+<!ELEMENT action (param|result|interceptor-ref|external-ref)*>
 <!ATTLIST action
 name CDATA #REQUIRED
     class CDATA #REQUIRED

File src/java/com/opensymphony/xwork/config/ExternalReferenceResolver.java

+/*
+ * 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;
+}

File src/java/com/opensymphony/xwork/config/ReferenceResolverException.java

+/*
+ * 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);
+	}
+
+}

File src/java/com/opensymphony/xwork/config/entities/ActionConfig.java

 import java.lang.reflect.Method;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
     //~ Instance fields ////////////////////////////////////////////////////////
 
     protected List interceptors;
+    protected List externalRefs;
     protected Map params;
     protected Map results;
     protected Method method;
     protected String methodName;
+	protected String packageName;
     private Class clazz;
 
     //~ Constructors ///////////////////////////////////////////////////////////
         params = new HashMap();
         results = new HashMap();
         interceptors = new ArrayList();
-    }
-
-    public ActionConfig(String methodName, Class clazz, Map parameters, Map results, List interceptors) {
+		externalRefs = new ArrayList();
+    }
+    //Helper constuctor to maintain backward compatibility with objects that create ActionConfigs
+    //TODO this should be removed if these changes are rolled in to xwork CVS
+	public ActionConfig(String methodName, Class clazz, Map parameters, Map results, List interceptors) 
+	{
+		this(methodName, clazz, parameters, results, interceptors, Collections.EMPTY_LIST, new String());
+	}
+	
+	//TODO If this is commited to CVS we should put the package arg at the front of the ctor and fix
+	//code that uses it
+    public ActionConfig(String methodName, Class clazz, Map parameters, Map results, List interceptors, List externalRefs, String packageName) {
         this.methodName = methodName;
         this.interceptors = interceptors;
         this.params = parameters;
         this.results = results;
         this.clazz = clazz;
+        this.externalRefs = externalRefs;
+        this.packageName = packageName;
     }
 
     //~ Methods ////////////////////////////////////////////////////////////////
     public void addInterceptors(List interceptors) {
         getInterceptors().addAll(interceptors);
     }
-
+    
     public void addParam(String name, Object value) {
         getParams().put(name, value);
     }
     public void addResultConfig(ResultConfig resultConfig) {
         getResults().put(resultConfig.getName(), resultConfig);
     }
-
+    
+	public void addExternalRef(ExternalReference reference) {
+		getExternalRefs().add(reference);
+	}
+
+	public void addExternalRefs(List externalRefs) {
+		getExternalRefs().addAll(externalRefs);
+	}
+	
+	public List getExternalRefs()
+	{
+		return externalRefs;
+	}
+	
     public boolean equals(Object o) {
         if (this == o) {
             return true;
 
         return result;
     }
-
-
+	/**
+	 * @return Returns the packageName.
+	 */
+	public String getPackageName() {
+		return packageName;
+	}
+
+	/**
+	 * @param packageName The packageName to set.
+	 */
+	public void setPackageName(String packageName) {
+		this.packageName = packageName;
+	}
+    
     public String toString() {
         return "{ActionConfig " + clazz.getName() + (methodName != null ? "." + methodName + "()" : "") + "}";
     }

File src/java/com/opensymphony/xwork/config/entities/ExternalReference.java

+/*
+ * 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;
+	}
+
+}

File src/java/com/opensymphony/xwork/config/entities/PackageConfig.java

 package com.opensymphony.xwork.config.entities;
 
 import com.opensymphony.util.TextUtils;
+import com.opensymphony.xwork.config.ExternalReferenceResolver;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
     private String name;
     private String namespace = "";
     private boolean isAbstract = false;
+    private ExternalReferenceResolver externalRefResolver = null;
 
     //~ Constructors ///////////////////////////////////////////////////////////
 
         this.name = name;
     }
 
-    public PackageConfig(String name, String namespace, boolean isAbstract) {
+    public PackageConfig(String name, String namespace, boolean isAbstract, ExternalReferenceResolver externalRefResolver) {
         this(name);
         this.namespace = TextUtils.noNull(namespace);
         this.isAbstract = isAbstract;
+        this.externalRefResolver = externalRefResolver;
     }
 
-    public PackageConfig(String name, String namespace, boolean isAbstract, List parents) {
-        this(name, namespace, isAbstract);
+    public PackageConfig(String name, String namespace, boolean isAbstract, ExternalReferenceResolver externalRefResolver, List parents) {
+        this(name, namespace, isAbstract, externalRefResolver);
 
         for (Iterator iterator = parents.iterator(); iterator.hasNext();) {
             PackageConfig parent = (PackageConfig) iterator.next();
     public String toString() {
         return "{PackageConfig Name:" + name + " namespace:" + namespace + " abstract:" + isAbstract + " parents:" + parents + "}";
     }
+    
+	/**
+	 * Gets the Reference resolver for this package.  If the resolver for this package is
+	 * not specified, the method will try and find one on one of the parent packages
+	 * @return Returns the externalRefResolver.
+	 */
+	public ExternalReferenceResolver getExternalRefResolver() {
+		//If this resolver is null, lets look to see if our parents have one
+		if(externalRefResolver==null) {
+			PackageConfig packageConfig;
+			for(Iterator iter = getParents().iterator(); iter.hasNext();)
+			{
+				packageConfig = (PackageConfig)iter.next();
+				if(packageConfig.getExternalRefResolver() != null )
+				{
+					externalRefResolver = packageConfig.getExternalRefResolver();
+					break;
+				}
+			}
+		}
+		return externalRefResolver;
+	}
+
+	/**
+	 * @param externalRefResolver The externalRefResolver to set.
+	 */
+	public void setExternalRefResolver(ExternalReferenceResolver externalRefResolver) {
+		this.externalRefResolver = externalRefResolver;
+	}
+
 }

File src/java/com/opensymphony/xwork/config/impl/DefaultConfiguration.java

                 interceptors.addAll(InterceptorBuilder.constructInterceptorReference(packageContext, defaultInterceptorRefName, new HashMap()));
             }
         }
-
-        ActionConfig config = new ActionConfig(baseConfig.getMethodName(), baseConfig.getClazz(), params, results, interceptors);
+        
+        List externalRefs = baseConfig.getExternalRefs();
+        
+        ActionConfig config = new ActionConfig(baseConfig.getMethodName(), baseConfig.getClazz(), 
+        		params, results, interceptors, baseConfig.getExternalRefs(), packageContext.getName());
 
         return config;
     }

File src/java/com/opensymphony/xwork/config/providers/XmlConfigurationProvider.java

 import com.opensymphony.xwork.config.ConfigurationException;
 import com.opensymphony.xwork.config.ConfigurationProvider;
 import com.opensymphony.xwork.config.ConfigurationUtil;
+import com.opensymphony.xwork.config.ExternalReferenceResolver;
 import com.opensymphony.xwork.config.entities.ActionConfig;
+import com.opensymphony.xwork.config.entities.ExternalReference;
 import com.opensymphony.xwork.config.entities.InterceptorConfig;
 import com.opensymphony.xwork.config.entities.InterceptorStackConfig;
 import com.opensymphony.xwork.config.entities.PackageConfig;
         }
 
         List interceptorList = buildInterceptorList(actionElement, packageContext);
-
-        ActionConfig actionConfig = new ActionConfig(methodName, clazz, actionParams, results, interceptorList);
+        
+		List externalrefs = buildExternalRefs(actionElement, packageContext);
+		
+        ActionConfig actionConfig = new ActionConfig(methodName, clazz, actionParams, results, interceptorList, externalrefs, packageContext.getName());
         packageContext.addActionConfig(name, actionConfig);
 
         if (LOG.isDebugEnabled())
             LOG.debug("Loaded " + (TextUtils.stringSet(packageContext.getNamespace()) ? packageContext.getNamespace() + "/" : "")
                     + name + " in '" + packageContext.getName() + "' package:" + actionConfig);
+
     }
 
     /**
 
         return interceptorList;
     }
+    
+	protected List buildExternalRefs(Element element, PackageConfig context) throws ConfigurationException {
+		List refs = new ArrayList();
+		NodeList externalRefList = element.getElementsByTagName("external-ref");
+		
+		String refName = null;
+		String refValue = null;
+		String requiredTemp = null;
+		boolean required;
+		for (int i = 0; i < externalRefList.getLength(); i++) {
+			Element refElement = (Element) externalRefList.item(i);
+
+			if (refElement.getParentNode().equals(element)) {
+				refName = refElement.getAttribute("name");
+				
+				//If the external ref is not declared explicitly, we can introspect the
+				//reference type using it's name and try resolving the reference using it's class type
+				if(refElement.getChildNodes().getLength() > 0)
+				{    
+				    refValue = refElement.getChildNodes().item(0).getNodeValue();
+				}
+				requiredTemp = refElement.getAttribute("required");
+				if("".equals(requiredTemp)) {
+					required = true;
+				} else {
+					required = Boolean.valueOf(requiredTemp).booleanValue();
+				}
+				refs.add(new ExternalReference(refName, refValue, required));
+			}
+		}
+
+		return refs;
+	}
 
     /**
     * This method builds a package context by looking for the parents of this new package.
         boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue();
         String name = TextUtils.noNull(packageElement.getAttribute("name"));
         String namespace = TextUtils.noNull(packageElement.getAttribute("namespace"));
-
-        if (!TextUtils.stringSet(TextUtils.noNull(parent))) { // no parents
-
-            return new PackageConfig(name, namespace, isAbstract);
+		
+        //RM* Load the ExternalReferenceResolver if one has been set
+		ExternalReferenceResolver erResolver = null;
+		
+        String externalReferenceResolver = TextUtils.noNull(packageElement.getAttribute("externalReferenceResolver"));
+		
+		if(!("".equals(externalReferenceResolver)))
+		{	
+	        try {
+				Class erResolverClazz = ClassLoaderUtil.loadClass(externalReferenceResolver, ExternalReferenceResolver.class);
+				
+			    erResolver = (ExternalReferenceResolver) erResolverClazz.newInstance();
+				
+	        } catch (ClassNotFoundException e) {
+	        	//TODO this should be localized
+	        	String msg = "Could not find External Reference Resolver: " + externalReferenceResolver + ". " + e.getMessage();
+				LOG.error(msg);
+				throw new ConfigurationException(msg, e);
+			} catch (Exception e)
+			{
+				//TODO this should be localized
+				String msg = "Could not create External Reference Resolver: " + externalReferenceResolver + ". " + e.getMessage();
+				LOG.error(msg);
+				throw new ConfigurationException(msg, e);
+			}
+		}
+		
+		if (!TextUtils.stringSet(TextUtils.noNull(parent))) { // no parents
+
+            return new PackageConfig(name, namespace, isAbstract, erResolver);
         } else { // has parents, let's look it up
 
             List parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
             if (parents.size() <= 0) {
                 LOG.error("Unable to find parent packages " + parent);
 
-                return new PackageConfig(name, namespace, isAbstract);
+                return new PackageConfig(name, namespace, isAbstract, erResolver);
             } else {
-                return new PackageConfig(name, namespace, isAbstract, parents);
+                return new PackageConfig(name, namespace, isAbstract, erResolver, parents);
             }
         }
     }

File src/java/com/opensymphony/xwork/interceptor/ExternalReferencesInterceptor.java

+/*
+ * 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);
+			}
+		}
+	}
+}

File src/test/com/opensymphony/xwork/ExternalReferenceAction.java

+/*
+ * 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;
+	}
+
+}

File src/test/com/opensymphony/xwork/Foo.java

+/*
+ * 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;
+	}
+}

File src/test/com/opensymphony/xwork/config/ExternalReferenceResolverTest.java

+/*
+ * 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());
+
+	}
+}

File src/test/com/opensymphony/xwork/config/TestExternalReferenceResolver.java

+/*
+ * 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 ognl.Ognl;
+
+import com.opensymphony.xwork.ActionInvocation;
+import com.opensymphony.xwork.Foo;
+import com.opensymphony.xwork.config.entities.ExternalReference;
+import com.opensymphony.xwork.util.OgnlUtil;
+
+
+/**
+ * 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 {
+				Map context = Ognl.createDefaultContext(invocation.getAction());
+			    OgnlUtil.setProperty(reference.getName(), obj, invocation.getAction(), context);
+				
+			} 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;
+	}
+
+}

File src/test/com/opensymphony/xwork/config/providers/XmlConfigurationProviderPackagesTest.java

         provider.init(configuration);
 
         // setup our expectations
-        PackageConfig expectedNamespacePackage = new PackageConfig("namespacepkg", "/namespace/set", false);
-        PackageConfig expectedAbstractPackage = new PackageConfig("abstractpkg", null, true);
+        PackageConfig expectedNamespacePackage = new PackageConfig("namespacepkg", "/namespace/set", false, null);
+        PackageConfig expectedAbstractPackage = new PackageConfig("abstractpkg", null, true, null);
 
         // test expectations
         assertEquals(3, configuration.getPackageConfigs().size());
         // setup our expectations
         PackageConfig defaultPackage = new PackageConfig("default");
 
-        PackageConfig abstractPackage = new PackageConfig("abstractPackage", null, true);
+        PackageConfig abstractPackage = new PackageConfig("abstractPackage", null, true, null);
 
         PackageConfig singleInheritancePackage = new PackageConfig("singleInheritance");
         singleInheritancePackage.addParent(defaultPackage);

File src/test/xwork-default.xml

             <interceptor name="test" class="com.opensymphony.xwork.MockInterceptor">
                 <param name="foo">expectedFoo</param>
             </interceptor>
+            <interceptor name="reference-resolver" class="com.opensymphony.xwork.interceptor.ExternalReferencesInterceptor"/>
 
             <interceptor-stack name="defaultStack">
+            	<interceptor-ref name="reference-resolver"/>
                 <interceptor-ref name="static-params"/>
                 <interceptor-ref name="model-driven"/>
                 <interceptor-ref name="params"/>

File src/test/xwork.xml

     "-//OpenSymphony Group//XWork 1.0//EN"
     "http://www.opensymphony.com/xwork/xwork-1.0.dtd"
  >
+ <!-- "file:///temp/ross/xwork/src/etc/xwork-1.0.dtd"  -->
 
 <xwork>
     <include file="xwork-default.xml"/>
-    <package name="default" extends="xwork-default">
+    <package name="default" extends="xwork-default" externalReferenceResolver="com.opensymphony.xwork.config.TestExternalReferenceResolver">
         <global-results>
             <result name="login"> <!-- should be chain type since it is the default -->
                 <param name="actionName">login</param>
             <interceptor-ref name="defaultStack"/>
             <interceptor-ref name="validation"/>
         </action>
-    </package>
+        <!-- test resolution  -->
+        <action name="TestExternalRefResolver" class="com.opensymphony.xwork.ExternalReferenceAction">
+        	<external-ref name="foo">myFoo</external-ref>
+            <interceptor-ref name="debugStack"/>
+            <interceptor-ref name="defaultStack"/>
+        </action>
+        <!-- test required flag -->
+        <action name="TestExternalRefResolver2" class="com.opensymphony.xwork.ExternalReferenceAction">
+        	<external-ref name="foo">myFoo</external-ref>
+        	<external-ref required="false" name="does_not_exist">Does Not Exist</external-ref>
+            <interceptor-ref name="debugStack"/>
+            <interceptor-ref name="defaultStack"/>
+        </action>
+        <!-- test where required reference is not found -->
+        <action name="TestExternalRefResolver3" class="com.opensymphony.xwork.ExternalReferenceAction">
+        	<external-ref required="true" name="Boo">myBoo</external-ref>
+            <interceptor-ref name="debugStack"/>
+            <interceptor-ref name="defaultStack"/>
+        </action>
+   	</package>
+   	
+   	<package name="test-external-refs" extends="default" namespace="test/externalRef/">
+        <!-- test resolution when Resolver is declared on parent -->
+        <action name="TestExternalRefResolver4" class="com.opensymphony.xwork.ExternalReferenceAction">
+        	<external-ref name="foo">myFoo</external-ref>
+            <interceptor-ref name="defaultStack"/>
+        </action>
+   </package>
 
     <package name="bar" extends="default" namespace="/foo/bar">
         <interceptors>