001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.beans.factory.xml;
018:
019: import org.w3c.dom.Attr;
020: import org.w3c.dom.Element;
021: import org.w3c.dom.NamedNodeMap;
022:
023: import org.springframework.beans.factory.support.BeanDefinitionBuilder;
024: import org.springframework.core.Conventions;
025: import org.springframework.util.Assert;
026: import 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</code> 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</code> 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</code>
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</code> 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</code> 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/></code> element matches the
094: * {@link org.springframework.beans.factory.config.PropertiesFactoryBean#setLocation(org.springframework.core.io.Resource)}
095: * method name on the <code>PropertiesFactoryBean</code> (the general
096: * usage thus illustrated holds true for any number of attributes).
097: * All that the <code>PropertiesBeanDefinitionParser</code> needs
098: * actually do is supply an implementation of the
099: * {@link #getBeanClass(org.w3c.dom.Element)} method to return the
100: * <code>PropertiesFactoryBean</code> type.
101: *
102: * @author Rob Harrop
103: * @author Rick Evans
104: * @author Juergen Hoeller
105: * @since 2.0
106: * @see Conventions#attributeNameToPropertyName(String)
107: */
108: public abstract class AbstractSimpleBeanDefinitionParser extends
109: AbstractSingleBeanDefinitionParser {
110:
111: /**
112: * Parse the supplied {@link Element} and populate the supplied
113: * {@link BeanDefinitionBuilder} as required.
114: * <p>This implementation maps any attributes present on the
115: * supplied element to {@link org.springframework.beans.PropertyValue}
116: * instances, and
117: * {@link BeanDefinitionBuilder#addPropertyValue(String, Object) adds them}
118: * to the
119: * {@link org.springframework.beans.factory.config.BeanDefinition builder}.
120: * <p>The {@link #extractPropertyName(String)} method is used to
121: * reconcile the name of an attribute with the name of a JavaBean
122: * property.
123: * @param element the XML element being parsed
124: * @param builder used to define the <code>BeanDefinition</code>
125: * @see #extractPropertyName(String)
126: */
127: protected final void doParse(Element element,
128: BeanDefinitionBuilder builder) {
129: NamedNodeMap attributes = element.getAttributes();
130: for (int x = 0; x < attributes.getLength(); x++) {
131: Attr attribute = (Attr) attributes.item(x);
132: String name = attribute.getLocalName();
133: if (isEligibleAttribute(name)) {
134: String propertyName = extractPropertyName(name);
135: Assert
136: .state(
137: StringUtils.hasText(propertyName),
138: "Illegal property name returned from 'extractPropertyName(String)': cannot be null or empty.");
139: builder.addPropertyValue(propertyName, attribute
140: .getValue());
141: }
142: }
143: postProcess(builder, element);
144: }
145:
146: /**
147: * Determine whether the given attribute is eligible for being
148: * turned into a corresponding bean property value.
149: * <p>The default implementation considers any attribute as eligible,
150: * except for the "id" attribute.
151: * @param attributeName the attribute name taken straight from the
152: * XML element being parsed (never <code>null</code>)
153: */
154: protected boolean isEligibleAttribute(String attributeName) {
155: return !ID_ATTRIBUTE.equals(attributeName);
156: }
157:
158: /**
159: * Extract a JavaBean property name from the supplied attribute name.
160: * <p>The default implementation uses the
161: * {@link Conventions#attributeNameToPropertyName(String)}
162: * method to perform the extraction.
163: * <p>The name returned must obey the standard JavaBean property name
164: * conventions. For example for a class with a setter method
165: * '<code>setBingoHallFavourite(String)</code>', the name returned had
166: * better be '<code>bingoHallFavourite</code>' (with that exact casing).
167: * @param attributeName the attribute name taken straight from the
168: * XML element being parsed (never <code>null</code>)
169: * @return the extracted JavaBean property name (must never be <code>null</code>)
170: */
171: protected String extractPropertyName(String attributeName) {
172: return Conventions.attributeNameToPropertyName(attributeName);
173: }
174:
175: /**
176: * Hook method that derived classes can implement to inspect/change a
177: * bean definition after parsing is complete.
178: * <p>The default implementation does nothing.
179: * @param beanDefinition the parsed (and probably totally defined) bean definition being built
180: * @param element the XML element that was the source of the bean definition's metadata
181: */
182: protected void postProcess(BeanDefinitionBuilder beanDefinition,
183: Element element) {
184: }
185:
186: }
|