001/* 002 * Copyright 2002-2014 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 org.w3c.dom.Attr; 020import org.w3c.dom.Element; 021import org.w3c.dom.NamedNodeMap; 022 023import org.springframework.beans.factory.support.BeanDefinitionBuilder; 024import org.springframework.core.Conventions; 025import org.springframework.util.Assert; 026import org.springframework.util.StringUtils; 027 028/** 029 * Convenient base class for when there exists a one-to-one mapping 030 * between attribute names on the element that is to be parsed and 031 * the property names on the {@link Class} being configured. 032 * 033 * <p>Extend this parser class when you want to create a single 034 * bean definition from a relatively simple custom XML element. The 035 * resulting {@code BeanDefinition} will be automatically 036 * registered with the relevant 037 * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}. 038 * 039 * <p>An example will hopefully make the use of this particular parser 040 * class immediately clear. Consider the following class definition: 041 * 042 * <pre class="code">public class SimpleCache implements Cache { 043 * 044 * public void setName(String name) {...} 045 * public void setTimeout(int timeout) {...} 046 * public void setEvictionPolicy(EvictionPolicy policy) {...} 047 * 048 * // remaining class definition elided for clarity... 049 * }</pre> 050 * 051 * <p>Then let us assume the following XML tag has been defined to 052 * permit the easy configuration of instances of the above class; 053 * 054 * <pre class="code"><caching:cache name="..." timeout="..." eviction-policy="..."/></pre> 055 * 056 * <p>All that is required of the Java developer tasked with writing 057 * the parser to parse the above XML tag into an actual 058 * {@code SimpleCache} bean definition is the following: 059 * 060 * <pre class="code">public class SimpleCacheBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { 061 * 062 * protected Class getBeanClass(Element element) { 063 * return SimpleCache.class; 064 * } 065 * }</pre> 066 * 067 * <p>Please note that the {@code AbstractSimpleBeanDefinitionParser} 068 * is limited to populating the created bean definition with property values. 069 * if you want to parse constructor arguments and nested elements from the 070 * supplied XML element, then you will have to implement the 071 * {@link #postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.w3c.dom.Element)} 072 * method and do such parsing yourself, or (more likely) subclass the 073 * {@link AbstractSingleBeanDefinitionParser} or {@link AbstractBeanDefinitionParser} 074 * classes directly. 075 * 076 * <p>The process of actually registering the 077 * {@code SimpleCacheBeanDefinitionParser} with the Spring XML parsing 078 * infrastructure is described in the Spring Framework reference documentation 079 * (in one of the appendices). 080 * 081 * <p>For an example of this parser in action (so to speak), do look at 082 * the source code for the 083 * {@link org.springframework.beans.factory.xml.UtilNamespaceHandler.PropertiesBeanDefinitionParser}; 084 * the observant (and even not so observant) reader will immediately notice that 085 * there is next to no code in the implementation. The 086 * {@code PropertiesBeanDefinitionParser} populates a 087 * {@link org.springframework.beans.factory.config.PropertiesFactoryBean} 088 * from an XML element that looks like this: 089 * 090 * <pre class="code"><util:properties location="jdbc.properties"/></pre> 091 * 092 * <p>The observant reader will notice that the sole attribute on the 093 * {@code <util:properties/>} element matches the 094 * {@link org.springframework.beans.factory.config.PropertiesFactoryBean#setLocation(org.springframework.core.io.Resource)} 095 * method name on the {@code PropertiesFactoryBean} (the general 096 * usage thus illustrated holds true for any number of attributes). 097 * All that the {@code PropertiesBeanDefinitionParser} needs 098 * actually do is supply an implementation of the 099 * {@link #getBeanClass(org.w3c.dom.Element)} method to return the 100 * {@code PropertiesFactoryBean} type. 101 * 102 * @author Rob Harrop 103 * @author Rick Evans 104 * @author Juergen Hoeller 105 * @since 2.0 106 * @see Conventions#attributeNameToPropertyName(String) 107 */ 108public abstract class AbstractSimpleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 109 110 /** 111 * Parse the supplied {@link Element} and populate the supplied 112 * {@link BeanDefinitionBuilder} as required. 113 * <p>This implementation maps any attributes present on the 114 * supplied element to {@link org.springframework.beans.PropertyValue} 115 * instances, and 116 * {@link BeanDefinitionBuilder#addPropertyValue(String, Object) adds them} 117 * to the 118 * {@link org.springframework.beans.factory.config.BeanDefinition builder}. 119 * <p>The {@link #extractPropertyName(String)} method is used to 120 * reconcile the name of an attribute with the name of a JavaBean 121 * property. 122 * @param element the XML element being parsed 123 * @param builder used to define the {@code BeanDefinition} 124 * @see #extractPropertyName(String) 125 */ 126 @Override 127 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { 128 NamedNodeMap attributes = element.getAttributes(); 129 for (int x = 0; x < attributes.getLength(); x++) { 130 Attr attribute = (Attr) attributes.item(x); 131 if (isEligibleAttribute(attribute, parserContext)) { 132 String propertyName = extractPropertyName(attribute.getLocalName()); 133 Assert.state(StringUtils.hasText(propertyName), 134 "Illegal property name returned from 'extractPropertyName(String)': cannot be null or empty."); 135 builder.addPropertyValue(propertyName, attribute.getValue()); 136 } 137 } 138 postProcess(builder, element); 139 } 140 141 /** 142 * Determine whether the given attribute is eligible for being 143 * turned into a corresponding bean property value. 144 * <p>The default implementation considers any attribute as eligible, 145 * except for the "id" attribute and namespace declaration attributes. 146 * @param attribute the XML attribute to check 147 * @param parserContext the {@code ParserContext} 148 * @see #isEligibleAttribute(String) 149 */ 150 protected boolean isEligibleAttribute(Attr attribute, ParserContext parserContext) { 151 String fullName = attribute.getName(); 152 return (!fullName.equals("xmlns") && !fullName.startsWith("xmlns:") && 153 isEligibleAttribute(parserContext.getDelegate().getLocalName(attribute))); 154 } 155 156 /** 157 * Determine whether the given attribute is eligible for being 158 * turned into a corresponding bean property value. 159 * <p>The default implementation considers any attribute as eligible, 160 * except for the "id" attribute. 161 * @param attributeName the attribute name taken straight from the 162 * XML element being parsed (never {@code null}) 163 */ 164 protected boolean isEligibleAttribute(String attributeName) { 165 return !ID_ATTRIBUTE.equals(attributeName); 166 } 167 168 /** 169 * Extract a JavaBean property name from the supplied attribute name. 170 * <p>The default implementation uses the 171 * {@link Conventions#attributeNameToPropertyName(String)} 172 * method to perform the extraction. 173 * <p>The name returned must obey the standard JavaBean property name 174 * conventions. For example for a class with a setter method 175 * '{@code setBingoHallFavourite(String)}', the name returned had 176 * better be '{@code bingoHallFavourite}' (with that exact casing). 177 * @param attributeName the attribute name taken straight from the 178 * XML element being parsed (never {@code null}) 179 * @return the extracted JavaBean property name (must never be {@code null}) 180 */ 181 protected String extractPropertyName(String attributeName) { 182 return Conventions.attributeNameToPropertyName(attributeName); 183 } 184 185 /** 186 * Hook method that derived classes can implement to inspect/change a 187 * bean definition after parsing is complete. 188 * <p>The default implementation does nothing. 189 * @param beanDefinition the parsed (and probably totally defined) bean definition being built 190 * @param element the XML element that was the source of the bean definition's metadata 191 */ 192 protected void postProcess(BeanDefinitionBuilder beanDefinition, Element element) { 193 } 194 195}