001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.factory.xml;
018
019import java.util.Collection;
020
021import org.w3c.dom.Attr;
022import org.w3c.dom.Element;
023import org.w3c.dom.Node;
024
025import org.springframework.beans.factory.config.BeanDefinition;
026import org.springframework.beans.factory.config.BeanDefinitionHolder;
027import org.springframework.beans.factory.config.ConstructorArgumentValues;
028import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
029import org.springframework.beans.factory.config.RuntimeBeanReference;
030import org.springframework.core.Conventions;
031import org.springframework.util.StringUtils;
032
033/**
034 * Simple {@code NamespaceHandler} implementation that maps custom
035 * attributes directly through to bean properties. An important point to note is
036 * that this {@code NamespaceHandler} does not have a corresponding schema
037 * since there is no way to know in advance all possible attribute names.
038 *
039 * <p>An example of the usage of this {@code NamespaceHandler} is shown below:
040 *
041 * <pre class="code">
042 * &lt;bean id=&quot;author&quot; class=&quot;..TestBean&quot; c:name=&quot;Enescu&quot; c:work-ref=&quot;compositions&quot;/&gt;
043 * </pre>
044 *
045 * Here the '{@code c:name}' corresponds directly to the '{@code name}
046 * ' argument declared on the constructor of class '{@code TestBean}'. The
047 * '{@code c:work-ref}' attributes corresponds to the '{@code work}'
048 * argument and, rather than being the concrete value, it contains the name of
049 * the bean that will be considered as a parameter.
050 *
051 * <b>Note</b>: This implementation supports only named parameters - there is no
052 * support for indexes or types. Further more, the names are used as hints by
053 * the container which, by default, does type introspection.
054 *
055 * @author Costin Leau
056 * @since 3.1
057 * @see SimplePropertyNamespaceHandler
058 */
059public class SimpleConstructorNamespaceHandler implements NamespaceHandler {
060
061        private static final String REF_SUFFIX = "-ref";
062
063        private static final String DELIMITER_PREFIX = "_";
064
065
066        @Override
067        public void init() {
068        }
069
070        @Override
071        public BeanDefinition parse(Element element, ParserContext parserContext) {
072                parserContext.getReaderContext().error(
073                                "Class [" + getClass().getName() + "] does not support custom elements.", element);
074                return null;
075        }
076
077        @Override
078        public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
079                if (node instanceof Attr) {
080                        Attr attr = (Attr) node;
081                        String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));
082                        String argValue = StringUtils.trimWhitespace(attr.getValue());
083
084                        ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();
085                        boolean ref = false;
086
087                        // handle -ref arguments
088                        if (argName.endsWith(REF_SUFFIX)) {
089                                ref = true;
090                                argName = argName.substring(0, argName.length() - REF_SUFFIX.length());
091                        }
092
093                        ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue);
094                        valueHolder.setSource(parserContext.getReaderContext().extractSource(attr));
095
096                        // handle "escaped"/"_" arguments
097                        if (argName.startsWith(DELIMITER_PREFIX)) {
098                                String arg = argName.substring(1).trim();
099
100                                // fast default check
101                                if (!StringUtils.hasText(arg)) {
102                                        cvs.addGenericArgumentValue(valueHolder);
103                                }
104                                // assume an index otherwise
105                                else {
106                                        int index = -1;
107                                        try {
108                                                index = Integer.parseInt(arg);
109                                        }
110                                        catch (NumberFormatException ex) {
111                                                parserContext.getReaderContext().error(
112                                                                "Constructor argument '" + argName + "' specifies an invalid integer", attr);
113                                        }
114                                        if (index < 0) {
115                                                parserContext.getReaderContext().error(
116                                                                "Constructor argument '" + argName + "' specifies a negative index", attr);
117                                        }
118
119                                        if (cvs.hasIndexedArgumentValue(index)) {
120                                                parserContext.getReaderContext().error(
121                                                                "Constructor argument '" + argName + "' with index "+ index+" already defined using <constructor-arg>." +
122                                                                " Only one approach may be used per argument.", attr);
123                                        }
124
125                                        cvs.addIndexedArgumentValue(index, valueHolder);
126                                }
127                        }
128                        // no escaping -> ctr name
129                        else {
130                                String name = Conventions.attributeNameToPropertyName(argName);
131                                if (containsArgWithName(name, cvs)) {
132                                        parserContext.getReaderContext().error(
133                                                        "Constructor argument '" + argName + "' already defined using <constructor-arg>." +
134                                                        " Only one approach may be used per argument.", attr);
135                                }
136                                valueHolder.setName(Conventions.attributeNameToPropertyName(argName));
137                                cvs.addGenericArgumentValue(valueHolder);
138                        }
139                }
140                return definition;
141        }
142
143        private boolean containsArgWithName(String name, ConstructorArgumentValues cvs) {
144                return (checkName(name, cvs.getGenericArgumentValues()) ||
145                                checkName(name, cvs.getIndexedArgumentValues().values()));
146        }
147
148        private boolean checkName(String name, Collection<ValueHolder> values) {
149                for (ValueHolder holder : values) {
150                        if (name.equals(holder.getName())) {
151                                return true;
152                        }
153                }
154                return false;
155        }
156
157}