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.lang.Nullable; 032import org.springframework.util.StringUtils; 033 034/** 035 * Simple {@code NamespaceHandler} implementation that maps custom 036 * attributes directly through to bean properties. An important point to note is 037 * that this {@code NamespaceHandler} does not have a corresponding schema 038 * since there is no way to know in advance all possible attribute names. 039 * 040 * <p>An example of the usage of this {@code NamespaceHandler} is shown below: 041 * 042 * <pre class="code"> 043 * <bean id="author" class="..TestBean" c:name="Enescu" c:work-ref="compositions"/> 044 * </pre> 045 * 046 * Here the '{@code c:name}' corresponds directly to the '{@code name} 047 * ' argument declared on the constructor of class '{@code TestBean}'. The 048 * '{@code c:work-ref}' attributes corresponds to the '{@code work}' 049 * argument and, rather than being the concrete value, it contains the name of 050 * the bean that will be considered as a parameter. 051 * 052 * <b>Note</b>: This implementation supports only named parameters - there is no 053 * support for indexes or types. Further more, the names are used as hints by 054 * the container which, by default, does type introspection. 055 * 056 * @author Costin Leau 057 * @since 3.1 058 * @see SimplePropertyNamespaceHandler 059 */ 060public class SimpleConstructorNamespaceHandler implements NamespaceHandler { 061 062 private static final String REF_SUFFIX = "-ref"; 063 064 private static final String DELIMITER_PREFIX = "_"; 065 066 067 @Override 068 public void init() { 069 } 070 071 @Override 072 @Nullable 073 public BeanDefinition parse(Element element, ParserContext parserContext) { 074 parserContext.getReaderContext().error( 075 "Class [" + getClass().getName() + "] does not support custom elements.", element); 076 return null; 077 } 078 079 @Override 080 public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { 081 if (node instanceof Attr) { 082 Attr attr = (Attr) node; 083 String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr)); 084 String argValue = StringUtils.trimWhitespace(attr.getValue()); 085 086 ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues(); 087 boolean ref = false; 088 089 // handle -ref arguments 090 if (argName.endsWith(REF_SUFFIX)) { 091 ref = true; 092 argName = argName.substring(0, argName.length() - REF_SUFFIX.length()); 093 } 094 095 ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue); 096 valueHolder.setSource(parserContext.getReaderContext().extractSource(attr)); 097 098 // handle "escaped"/"_" arguments 099 if (argName.startsWith(DELIMITER_PREFIX)) { 100 String arg = argName.substring(1).trim(); 101 102 // fast default check 103 if (!StringUtils.hasText(arg)) { 104 cvs.addGenericArgumentValue(valueHolder); 105 } 106 // assume an index otherwise 107 else { 108 int index = -1; 109 try { 110 index = Integer.parseInt(arg); 111 } 112 catch (NumberFormatException ex) { 113 parserContext.getReaderContext().error( 114 "Constructor argument '" + argName + "' specifies an invalid integer", attr); 115 } 116 if (index < 0) { 117 parserContext.getReaderContext().error( 118 "Constructor argument '" + argName + "' specifies a negative index", attr); 119 } 120 121 if (cvs.hasIndexedArgumentValue(index)) { 122 parserContext.getReaderContext().error( 123 "Constructor argument '" + argName + "' with index "+ index+" already defined using <constructor-arg>." + 124 " Only one approach may be used per argument.", attr); 125 } 126 127 cvs.addIndexedArgumentValue(index, valueHolder); 128 } 129 } 130 // no escaping -> ctr name 131 else { 132 String name = Conventions.attributeNameToPropertyName(argName); 133 if (containsArgWithName(name, cvs)) { 134 parserContext.getReaderContext().error( 135 "Constructor argument '" + argName + "' already defined using <constructor-arg>." + 136 " Only one approach may be used per argument.", attr); 137 } 138 valueHolder.setName(Conventions.attributeNameToPropertyName(argName)); 139 cvs.addGenericArgumentValue(valueHolder); 140 } 141 } 142 return definition; 143 } 144 145 private boolean containsArgWithName(String name, ConstructorArgumentValues cvs) { 146 return (checkName(name, cvs.getGenericArgumentValues()) || 147 checkName(name, cvs.getIndexedArgumentValues().values())); 148 } 149 150 private boolean checkName(String name, Collection<ValueHolder> values) { 151 for (ValueHolder holder : values) { 152 if (name.equals(holder.getName())) { 153 return true; 154 } 155 } 156 return false; 157 } 158 159}