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">&lt;caching:cache name="..." timeout="..." eviction-policy="..."/&gt;</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">&lt;util:properties location="jdbc.properties"/&gt;</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}