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 }