diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java new file mode 100644 index 0000000000..81d8632bf5 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java @@ -0,0 +1,151 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.xml; + +import java.util.Collection; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.core.Conventions; +import org.springframework.util.StringUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Simple NamespaceHandler implementation that maps custom + * attributes directly through to bean properties. An important point to note is + * that this NamespaceHandler does not have a corresponding schema + * since there is no way to know in advance all possible attribute names. + * + *

+ * An example of the usage of this NamespaceHandler is shown below: + * + *

+ * <bean id="author" class="..TestBean" c:name="Enescu" c:work-ref="compositions"/>
+ * 
+ * + * Here the 'c:name' corresponds directly to the 'name + * ' argument declared on the constructor of class 'TestBean'. The + * 'c:work-ref' attributes corresponds to the 'work' + * argument and, rather than being the concrete value, it contains the name of + * the bean that will be considered as a parameter. + * + * Note: This implementation supports only named parameters - there is no + * support for indexes or types. Further more, the names are used as hints by + * the container which, by default, does type introspection. + * + * @see SimplePropertyNamespaceHandler + * @author Costin Leau + */ +public class SimpleConstructorNamespaceHandler implements NamespaceHandler { + + private static final String REF_SUFFIX = "-ref"; + private static final String DELIMITER_PREFIX = "_"; + + public void init() { + } + + public BeanDefinition parse(Element element, ParserContext parserContext) { + parserContext.getReaderContext().error( + "Class [" + getClass().getName() + "] does not support custom elements.", element); + return null; + } + + public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { + if (node instanceof Attr) { + Attr attr = (Attr) node; + String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr)); + String argValue = StringUtils.trimWhitespace(attr.getValue()); + + ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues(); + boolean ref = false; + + // handle -ref arguments + if (argName.endsWith(REF_SUFFIX)) { + ref = true; + argName = argName.substring(0, argName.length() - REF_SUFFIX.length()); + } + + ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue); + valueHolder.setSource(parserContext.getReaderContext().extractSource(attr)); + + // handle "escaped"/"_" arguments + if (argName.startsWith(DELIMITER_PREFIX)) { + String arg = argName.substring(1).trim(); + + // fast default check + if (!StringUtils.hasText(arg)) { + cvs.addGenericArgumentValue(valueHolder); + } + // assume an index otherwise + else { + int index = -1; + try { + index = Integer.parseInt(arg); + } catch (NumberFormatException ex) { + parserContext.getReaderContext().error( + "Constructor argument '" + argName + "' specifies an invalid integer", attr); + } + if (index < 0) { + parserContext.getReaderContext().error( + "Constructor argument '" + argName + "' specifies a negative index", attr); + } + + if (cvs.hasIndexedArgumentValue(index)){ + parserContext.getReaderContext().error( + "Constructor argument '" + argName + "' with index "+ index+" already defined using ." + + " Only one approach may be used per argument.", attr); + } + + cvs.addIndexedArgumentValue(index, valueHolder); + } + } + // no escaping -> ctr name + else { + String name = Conventions.attributeNameToPropertyName(argName); + if (containsArgWithName(name, cvs)){ + parserContext.getReaderContext().error( + "Constructor argument '" + argName + "' already defined using ." + + " Only one approach may be used per argument.", attr); + } + valueHolder.setName(Conventions.attributeNameToPropertyName(argName)); + cvs.addGenericArgumentValue(valueHolder); + } + } + return definition; + } + + private boolean containsArgWithName(String name, ConstructorArgumentValues cvs) { + if (!checkName(name, cvs.getGenericArgumentValues())) { + return checkName(name, cvs.getIndexedArgumentValues().values()); + } + + return true; + } + + private boolean checkName(String name, Collection values) { + for (ValueHolder holder : values) { + if (name.equals(holder.getName())) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/org.springframework.beans/src/main/resources/META-INF/spring.handlers b/org.springframework.beans/src/main/resources/META-INF/spring.handlers index 8fda43f0c7..80f1923094 100644 --- a/org.springframework.beans/src/main/resources/META-INF/spring.handlers +++ b/org.springframework.beans/src/main/resources/META-INF/spring.handlers @@ -1,2 +1,3 @@ +http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandlerTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandlerTests.java new file mode 100644 index 0000000000..86ddda5642 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandlerTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.xml; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.io.ClassPathResource; + +import test.beans.DummyBean; +import test.beans.TestBean; + +/** + * @author Costin Leau + */ +public class SimpleConstructorNamespaceHandlerTests { + + @Test + public void simpleValue() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + String name = "simple"; + // beanFactory.getBean("simple1", DummyBean.class); + DummyBean nameValue = beanFactory.getBean(name, DummyBean.class); + assertEquals("simple", nameValue.getValue()); + } + + @Test + public void simpleRef() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + String name = "simple-ref"; + // beanFactory.getBean("name-value1", TestBean.class); + DummyBean nameValue = beanFactory.getBean(name, DummyBean.class); + assertEquals(beanFactory.getBean("name"), nameValue.getValue()); + } + + @Test + public void nameValue() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + String name = "name-value"; + // beanFactory.getBean("name-value1", TestBean.class); + TestBean nameValue = beanFactory.getBean(name, TestBean.class); + assertEquals(name, nameValue.getName()); + assertEquals(10, nameValue.getAge()); + } + + @Test + public void nameRef() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + TestBean nameValue = beanFactory.getBean("name-value", TestBean.class); + DummyBean nameRef = beanFactory.getBean("name-ref", DummyBean.class); + + assertEquals("some-name", nameRef.getName()); + assertEquals(nameValue, nameRef.getSpouse()); + } + + @Test + public void typeIndexedValue() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + DummyBean typeRef = beanFactory.getBean("indexed-value", DummyBean.class); + + assertEquals("at", typeRef.getName()); + assertEquals("austria", typeRef.getValue()); + assertEquals(10, typeRef.getAge()); + } + + @Test + public void typeIndexedRef() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + DummyBean typeRef = beanFactory.getBean("indexed-ref", DummyBean.class); + + assertEquals("some-name", typeRef.getName()); + assertEquals(beanFactory.getBean("name-value"), typeRef.getSpouse()); + } + + @Test(expected = BeanDefinitionStoreException.class) + public void ambiguousConstructor() throws Exception { + new XmlBeanFactory(new ClassPathResource("simpleConstructorNamespaceHandlerTestsWithErrors.xml", getClass())); + } + + @Test + public void constructorWithNameEndingInRef() throws Exception { + XmlBeanFactory beanFactory = createFactory("simpleConstructorNamespaceHandlerTests.xml"); + DummyBean derivedBean = beanFactory.getBean("beanWithRefConstructorArg", DummyBean.class); + assertEquals(10, derivedBean.getAge()); + assertEquals("silly name", derivedBean.getName()); + } + + private XmlBeanFactory createFactory(String resourceName) { + XmlBeanFactory fact = new XmlBeanFactory(new ClassPathResource(resourceName, getClass())); + fact.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + return fact; + } +} \ No newline at end of file diff --git a/org.springframework.beans/src/test/resources/org/springframework/beans/factory/xml/simpleConstructorNamespaceHandlerTests.xml b/org.springframework.beans/src/test/resources/org/springframework/beans/factory/xml/simpleConstructorNamespaceHandlerTests.xml new file mode 100644 index 0000000000..f245c964dd --- /dev/null +++ b/org.springframework.beans/src/test/resources/org/springframework/beans/factory/xml/simpleConstructorNamespaceHandlerTests.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file