001    /* 
002     * Copyright 2008-2009 the original author or authors.
003     * The contents of this file are subject to the Mozilla Public License
004     * Version 1.1 (the "License"); you may not use this file except in
005     * compliance with the License. You may obtain a copy of the License at
006     * http://www.mozilla.org/MPL/
007     *
008     * Software distributed under the License is distributed on an "AS IS"
009     * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
010     * License for the specific language governing rights and limitations
011     * under the License.
012     */
013     
014    package com.mtgi.analytics.aop.config;
015    
016    import org.springframework.beans.MutablePropertyValues;
017    import org.springframework.beans.factory.BeanDefinitionStoreException;
018    import org.springframework.beans.factory.config.BeanDefinition;
019    import org.springframework.beans.factory.config.ConfigurableBeanFactory;
020    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
021    import org.springframework.beans.factory.config.RuntimeBeanReference;
022    import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
023    import org.springframework.beans.factory.support.AbstractBeanDefinition;
024    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
025    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
026    import org.springframework.beans.factory.support.RootBeanDefinition;
027    import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
028    import org.springframework.beans.factory.xml.ParserContext;
029    import org.springframework.beans.factory.xml.ResourceEntityResolver;
030    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
031    import org.springframework.core.Conventions;
032    import org.springframework.core.io.DefaultResourceLoader;
033    import org.w3c.dom.Element;
034    import org.w3c.dom.NodeList;
035    
036    /**
037     * Base class to assist in building Spring XML extensions.  Out of the box, Spring's Extensible XML Authoring
038     * support is powerful, but requires a lot of parser coding, and a lot of exposure to the sometimes
039     * arcane BeanDefinition API.  <code>TemplateBeanDefinitionParser</code> allows subclasses to read
040     * complex BeanDefinitions from an embedded Spring XML configuration file and then modify them according
041     * to runtime configuration values.  This is often much more concise than manually constructing BeanDefinitions 
042     * from scratch.
043     * 
044     * <p>Subclasses specify a classpath resource containing the template XML bean definitions in the constructor.
045     * This is just a standard Spring XML application context configuration file.
046     * Subclasses should then override {@link #transform(ConfigurableBeanFactory, BeanDefinition, Element, ParserContext)}
047     * to transform the template bean definition according to runtime values.</p>
048     * 
049     * <p>We also make use of {@link ChainingBeanFactoryPostProcessor} so that factory post-processing
050     * operations carry over into the bean factory containing template definitions.  This allows
051     * our custom tags' attributes to be subject to property replacement using PropertyPlaceholderConfigurer,
052     * for example.</p>
053     */
054    public class TemplateBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
055    
056            private String templateResource;
057            private String templateId;
058    
059            /**
060             * @param templateResource qualified classpath resource containing the template XML configuration
061             * @param templateId bean name to fetch out of the template XML configuration
062             */
063            public TemplateBeanDefinitionParser(String templateResource, String templateId) {
064                    this.templateResource = templateResource;
065                    this.templateId = templateId;
066            }
067    
068            @Override
069            protected Class<?> getBeanClass(Element element) {
070                    return TemplateBeanDefinitionFactory.class;
071            }
072    
073            /**
074             * <p>Load the template BeanDefinition and call {@link #transform(ConfigurableListableBeanFactory, BeanDefinition, Element, ParserContext)}
075             * to apply runtime configuration value to it.  <code>builder</code> will be configured to instantiate the bean
076             * in the Spring context that we are parsing.</p>
077             * 
078             * <p>During parsing, an instance of {@link TemplateComponentDefinition} is pushed onto <code>ParserContext</code> so
079             * that nested tags can access the enclosing template configuration with a call to {@link #findEnclosingTemplateFactory(ParserContext)}.
080             * Subclasses can override {@link #newComponentDefinition(String, Object, DefaultListableBeanFactory)} to provide a 
081             * subclass of {@link TemplateComponentDefinition} to the parser context if necessary.</p>
082             */
083            @Override
084            protected final void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
085    
086                    //if we have multiple nested bean definitions, we only parse the template factory
087                    //once.  this allows configuration changes made by enclosing bean parsers to be inherited
088                    //by contained beans, which is quite useful.
089                    DefaultListableBeanFactory templateFactory = findEnclosingTemplateFactory(parserContext);
090                    TemplateComponentDefinition tcd = null;
091                    if (templateFactory == null) {
092                            
093                            //no nesting -- load the template XML configuration from the classpath.
094                            final ConfigurableListableBeanFactory parentFactory = (ConfigurableListableBeanFactory)parserContext.getRegistry();
095                            templateFactory = new DefaultListableBeanFactory(parentFactory);
096                            
097                            //load template bean definitions
098                            DefaultResourceLoader loader = new DefaultResourceLoader();
099                            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(templateFactory);
100                            reader.setResourceLoader(loader);
101                            reader.setEntityResolver(new ResourceEntityResolver(loader));
102                            reader.loadBeanDefinitions(templateResource);
103    
104                            //propagate factory post-processors from the source factory into the template
105                            //factory.
106                            BeanDefinition ppChain = new RootBeanDefinition(ChainingBeanFactoryPostProcessor.class);
107                            ppChain.getPropertyValues().addPropertyValue("targetFactory", templateFactory);
108                            parserContext.getReaderContext().registerWithGeneratedName(ppChain);
109                            
110                            //push component definition onto the parser stack for the benefit of
111                            //nested bean definitions.
112                            tcd = newComponentDefinition(element.getNodeName(), parserContext.extractSource(element), templateFactory);
113                            parserContext.pushContainingComponent(tcd);
114                            
115                    }
116    
117                    try {
118                            //allow subclasses to apply overrides to the template bean definition.
119                            BeanDefinition def = templateFactory.getBeanDefinition(templateId);
120                            transform(templateFactory, def, element, parserContext);
121    
122                            //setup our factory bean to instantiate the modified bean definition upon request.
123                            builder.addPropertyValue("beanFactory", templateFactory);
124                            builder.addPropertyValue("beanName", templateId);
125                            builder.getRawBeanDefinition().setAttribute("id", def.getAttribute("id"));
126    
127                    } finally {
128                            if (tcd != null)
129                                    parserContext.popContainingComponent();
130                    }
131            }
132    
133            /**
134             * Hook by which subclasses can modify template configuration values.  Default behavior does nothing to the template.
135             * 
136             * @param template the template bean definition
137             * @param factory the bean factory from which <code>template</code> was loaded
138             * @param element XML configuration fragment containing overrides that should be applied to the template
139             * @param parserContext XML parse context supplying the configuration values
140             */
141            protected void transform(ConfigurableListableBeanFactory factory, BeanDefinition template, Element element, ParserContext parserContext) {
142            }
143    
144            /** create the component definition that will be pushed onto the parser context in {@link #doParse(Element, ParserContext, BeanDefinitionBuilder)}. */
145            protected TemplateComponentDefinition newComponentDefinition(String name, Object source, DefaultListableBeanFactory factory) {
146                    return new TemplateComponentDefinition(name, source, factory);
147            }
148            
149            /** 
150             * Overridden to prefer the <code>id</code> attribute of <code>definition</code> if it is defined, over whatever
151             * is in <code>element</code> (which would be the superclass behavior).  This allows subclasses to specify an ID
152             * value in {@link #transform(ConfigurableListableBeanFactory, BeanDefinition, Element, ParserContext)} if required. 
153             */
154            @Override
155            protected String resolveId(Element element,
156                            AbstractBeanDefinition definition, ParserContext parserContext)
157                            throws BeanDefinitionStoreException {
158                    String id = (String)definition.getAttribute("id");
159                    return id == null ? super.resolveId(element, definition, parserContext) : id;
160            }
161    
162            /** 
163             * returns true to prevent parse errors if an ID is not specified via the usual means, since we allow subclasses
164             * to generate bean IDs.  See {@link #resolveId(Element, AbstractBeanDefinition, ParserContext)}. 
165             */
166            @Override
167            protected boolean shouldGenerateIdAsFallback() {
168                    return true;
169            }
170            
171            /**
172             * Convenience method to update a template bean definition from overriding XML data.  
173             * If <code>overrides</code> contains attribute <code>attribute</code>, transfer that
174             * attribute onto <code>template</code>, overwriting the default value.
175             */
176            public static String overrideAttribute(String attribute, BeanDefinition template, Element overrides) {
177                    String value = (String)template.getAttribute(attribute);
178                    if (overrides.hasAttribute(attribute)) {
179                            value = overrides.getAttribute(attribute);
180                            template.setAttribute(attribute, value);
181                    }
182                    return value;
183            }
184            
185            /**
186             * Convenience method to update a template bean definition from overriding XML data.  
187             * If <code>overrides</code> contains attribute <code>attribute</code> or a child element
188             * with name <code>attribute</code>, transfer that
189             * attribute as a bean property onto <code>template</code>, overwriting the default value.
190             * @param reference if true, the value of the attribute is to be interpreted as a runtime bean name reference; otherwise it is interpreted as a literal value
191             */
192            public static boolean overrideProperty(String attribute, BeanDefinition template, Element overrides, boolean reference) {
193                    Object value = null;
194                    if (overrides.hasAttribute(attribute)) {
195                            value = overrides.getAttribute(attribute);
196                    } else {
197                            NodeList children = overrides.getElementsByTagNameNS("*", attribute);
198                            if (children.getLength() == 1) {
199                                    Element child = (Element)children.item(0);
200                                    value = child.getTextContent();
201                            }
202                    }
203                    
204                    if (value != null) {
205                            if (reference)
206                                    value = new RuntimeBeanReference(value.toString());
207                            
208                            String propName = Conventions.attributeNameToPropertyName(attribute);
209                            MutablePropertyValues props = template.getPropertyValues();
210                            props.removePropertyValue(propName);
211                            props.addPropertyValue(propName, value);
212                            return true;
213                    }
214                    
215                    return false;
216            }
217    
218            /** 
219             * If the given parse operation is nested inside an instance of {@link TemplateComponentDefinition}, return
220             * the template bean configuration associated with that component.  Otherwise return null.
221             */
222            private static DefaultListableBeanFactory findEnclosingTemplateFactory(ParserContext context) {
223                    if (context.isNested()) {
224                            CompositeComponentDefinition parent = context.getContainingComponent();
225                            if (parent instanceof TemplateComponentDefinition)
226                                    return ((TemplateComponentDefinition)parent).getTemplateFactory();
227                    }
228                    return null;
229            }
230            
231            /** 
232             * A component definition providing access to the template bean configuration, for the benefit
233             * of nested configuration tags.
234             */
235            public static class TemplateComponentDefinition extends CompositeComponentDefinition {
236    
237                    private DefaultListableBeanFactory templateFactory;
238                    
239                    public TemplateComponentDefinition(String name, Object source, DefaultListableBeanFactory factory) {
240                            super(name, source);
241                            this.templateFactory = factory;
242                    }
243    
244                    public DefaultListableBeanFactory getTemplateFactory() {
245                            return templateFactory;
246                    }
247            }
248    
249    }