001: /*
002: * $Id:AbstractMuleBeanDefinitionParser.java 5187 2007-02-16 18:00:42Z rossmason $
003: * --------------------------------------------------------------------------------------
004: * Copyright (c) MuleSource, Inc. All rights reserved. http://www.mulesource.com
005: *
006: * The software in this package is published under the terms of the CPAL v1.0
007: * license, a copy of which has been included with this distribution in the
008: * LICENSE.txt file.
009: */
010: package org.mule.config.spring.parsers;
011:
012: import org.mule.api.lifecycle.Disposable;
013: import org.mule.api.lifecycle.Initialisable;
014: import org.mule.config.spring.parsers.assembly.BeanAssembler;
015: import org.mule.config.spring.parsers.assembly.BeanAssemblerFactory;
016: import org.mule.config.spring.parsers.assembly.DefaultBeanAssemblerFactory;
017: import org.mule.config.spring.parsers.assembly.configuration.ReusablePropertyConfiguration;
018: import org.mule.config.spring.parsers.assembly.configuration.ValueMap;
019: import org.mule.config.spring.parsers.generic.AutoIdUtils;
020: import org.mule.config.spring.MuleHierarchicalBeanDefinitionParserDelegate;
021: import org.mule.util.ClassUtils;
022: import org.mule.util.XMLUtils;
023:
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027: import java.util.List;
028: import java.util.Map;
029: import java.util.Set;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.springframework.beans.factory.BeanDefinitionStoreException;
034: import org.springframework.beans.factory.support.AbstractBeanDefinition;
035: import org.springframework.beans.factory.support.BeanDefinitionBuilder;
036: import org.springframework.beans.factory.support.BeanDefinitionRegistry;
037: import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
038: import org.springframework.beans.factory.xml.ParserContext;
039: import org.w3c.dom.Attr;
040: import org.w3c.dom.Element;
041: import org.w3c.dom.NamedNodeMap;
042:
043: /**
044: * This parser extends the Spring provided {@link AbstractBeanDefinitionParser} to provide additional features for
045: * consistently customising bean representations for Mule bean definition parsers. Most custom bean definition parsers
046: * in Mule will use this base class. The following enhancements are made -
047: *
048: * <ol>
049: * <li>A property name which ends with the suffix "-ref" is assumed to be a reference to another bean.
050: * Alternatively, a property can be explicitly registered as a bean reference via registerBeanReference()
051: *
052: * <p>For example,
053: * <code> <bpm:connector bpms-ref="testBpms"/< </code>
054: * will automatically set a property "bpms" on the connector to reference a bean named "testBpms"
055: * </p></li>
056: *
057: * <li>Attribute mappings can be registered to control how an attribute name in Mule Xml maps to the bean name in the
058: * object being created.
059: *
060: * <p>For example -
061: * <code>addAlias("poolExhaustedAction", "poolExhaustedActionString");</code>
062: * Maps the 'poolExhaustedAction' to the 'poolExhaustedActionString' property on the bean being created.
063: * </p></li>
064: *
065: * <li>Value Mappings can be used to map key value pairs from selection lists in the XML schema to property values on the
066: * bean being created. These are a comma-separated list of key=value pairs.
067: *
068: * <p>For example -
069: * <code>addMapping("action", "NONE=0,ALWAYS_BEGIN=1,BEGIN_OR_JOIN=2,JOIN_IF_POSSIBLE=3");</code>
070: * The first argument is the bean name to set, the second argument is the set of possible key=value pairs
071: * </p></li>
072: *
073: * <li>Provides an automatic way of setting the 'init-method' and 'destroy-method' for this object. This will then automatically
074: * wire the bean into the lifecycle of the Application context.</li>
075: *
076: * <li>The 'singleton' property provides a fixed way to make sure the bean is always a singleton or not.</li>
077: *
078: * <li>Collections will be automatically created and extended if the setter matches "property+s".</li>
079: * </ol>
080: *
081: * <p>Note that this class is not multi-thread safe. The internal state is reset before each "use"
082: * by {@link #preProcess(org.w3c.dom.Element)} which assumes sequential access.</p>
083: *
084: * @see AbstractBeanDefinitionParser
085: */
086: public abstract class AbstractMuleBeanDefinitionParser extends
087: AbstractBeanDefinitionParser implements MuleDefinitionParser {
088:
089: public static final String ROOT_ELEMENT = "mule";
090: public static final String ATTRIBUTE_ID = "id";
091: public static final String ATTRIBUTE_NAME = "name";
092: public static final String ATTRIBUTE_CLASS = "class";
093: public static final String ATTRIBUTE_REF = "ref";
094: public static final String ATTRIBUTE_REFS = "refs";
095: public static final String ATTRIBUTE_REF_SUFFIX = "-"
096: + ATTRIBUTE_REF;
097: public static final String ATTRIBUTE_REFS_SUFFIX = "-"
098: + ATTRIBUTE_REFS;
099:
100: /**
101: * logger used by this class
102: */
103: protected transient Log logger = LogFactory.getLog(getClass());
104:
105: private BeanAssemblerFactory beanAssemblerFactory = new DefaultBeanAssemblerFactory();
106: protected ReusablePropertyConfiguration beanPropertyConfiguration = new ReusablePropertyConfiguration();
107: private ParserContext parserContext;
108: private BeanDefinitionRegistry registry;
109: private LinkedList preProcessors = new LinkedList();
110: private List postProcessors = new LinkedList();
111: private Set beanAttributes = new HashSet();
112: // By default Mule objects are not singletons
113: protected boolean singleton = false;
114:
115: /** Allow the bean class to be set explicitly via the "class" attribute. */
116: private boolean allowClassAttribute = true;
117: private Class classConstraint = null;
118:
119: public AbstractMuleBeanDefinitionParser() {
120: addIgnored(ATTRIBUTE_ID);
121: addBeanFlag(MuleHierarchicalBeanDefinitionParserDelegate.MULE_FORCE_RECURSE);
122: }
123:
124: public MuleDefinitionParserConfiguration addReference(
125: String propertyName) {
126: beanPropertyConfiguration.addReference(propertyName);
127: return this ;
128: }
129:
130: public MuleDefinitionParserConfiguration addMapping(
131: String propertyName, Map mappings) {
132: beanPropertyConfiguration.addMapping(propertyName, mappings);
133: return this ;
134: }
135:
136: public MuleDefinitionParserConfiguration addMapping(
137: String propertyName, String mappings) {
138: beanPropertyConfiguration.addMapping(propertyName, mappings);
139: return this ;
140: }
141:
142: public MuleDefinitionParserConfiguration addMapping(
143: String propertyName, ValueMap mappings) {
144: beanPropertyConfiguration.addMapping(propertyName, mappings);
145: return this ;
146: }
147:
148: /**
149: * @param alias The attribute name
150: * @param propertyName The bean property name
151: * @return This instance, allowing chaining during use, avoiding subclasses
152: */
153: public MuleDefinitionParserConfiguration addAlias(String alias,
154: String propertyName) {
155: beanPropertyConfiguration.addAlias(alias, propertyName);
156: return this ;
157: }
158:
159: /**
160: * @param propertyName Property that is a collection
161: * @return This instance, allowing chaining during use, avoiding subclasses
162: */
163: public MuleDefinitionParserConfiguration addCollection(
164: String propertyName) {
165: beanPropertyConfiguration.addCollection(propertyName);
166: return this ;
167: }
168:
169: /**
170: * @param propertyName Property that is to be ignored
171: * @return This instance, allowing chaining during use, avoiding subclasses
172: */
173: public MuleDefinitionParserConfiguration addIgnored(
174: String propertyName) {
175: beanPropertyConfiguration.addIgnored(propertyName);
176: return this ;
177: }
178:
179: public MuleDefinitionParserConfiguration removeIgnored(
180: String propertyName) {
181: beanPropertyConfiguration.removeIgnored(propertyName);
182: return this ;
183: }
184:
185: public MuleDefinitionParserConfiguration setIgnoredDefault(
186: boolean ignoreAll) {
187: beanPropertyConfiguration.setIgnoredDefault(ignoreAll);
188: return this ;
189: }
190:
191: protected void processProperty(Attr attribute,
192: BeanAssembler assembler) {
193: assembler.extendBean(attribute);
194: }
195:
196: /**
197: * Hook method that derived classes can implement to inspect/change a
198: * bean definition after parsing is complete.
199: *
200: * @param assembler the parsed (and probably totally defined) bean definition being built
201: * @param element the XML element that was the source of the bean definition's metadata
202: */
203: protected void postProcess(ParserContext context,
204: BeanAssembler assembler, Element element) {
205: element.setAttribute(ATTRIBUTE_NAME, getBeanName(element));
206: for (Iterator attributes = beanAttributes.iterator(); attributes
207: .hasNext();) {
208: assembler.setBeanFlag((String) attributes.next());
209: }
210: for (Iterator processes = postProcessors.iterator(); processes
211: .hasNext();) {
212: ((PostProcessor) processes.next()).postProcess(context,
213: assembler, element);
214: }
215: }
216:
217: /**
218: * Hook method that derived classes can implement to modify internal state before processing.
219: *
220: * Here we make sure that the internal property configuration state is reset to the
221: * initial configuration for each element (it may be modified by the BeanAssembler)
222: * and that other mutable instance variables are cleared.
223: */
224: protected void preProcess(Element element) {
225: parserContext = null;
226: registry = null;
227: beanPropertyConfiguration.reset();
228: Iterator processes = preProcessors.iterator();
229: while (processes.hasNext()) {
230: ((PreProcessor) processes.next()).preProcess(
231: beanPropertyConfiguration, element);
232: }
233: }
234:
235: /**
236: * Creates a {@link BeanDefinitionBuilder} instance for the
237: * {@link #getBeanClass bean Class} and passes it to the
238: * {@link #doParse} strategy method.
239: *
240: * @param element the element that is to be parsed into a single BeanDefinition
241: * @param parserContext the object encapsulating the current state of the parsing process
242: * @return the BeanDefinition resulting from the parsing of the supplied {@link Element}
243: * @throws IllegalStateException if the bean {@link Class} returned from
244: * {@link #getBeanClass(org.w3c.dom.Element)} is <code>null</code>
245: * @see #doParse
246: */
247: protected AbstractBeanDefinition parseInternal(Element element,
248: ParserContext parserContext) {
249: preProcess(element);
250: setParserContext(parserContext);
251: setRegistry(parserContext.getRegistry());
252: checkElementNameUnique(element);
253: Class beanClass = getClassInternal(element);
254: BeanDefinitionBuilder builder = createBeanDefinitionBuilder(
255: element, beanClass);
256: builder.setSource(parserContext.extractSource(element));
257: builder.setSingleton(isSingleton());
258:
259: List interfaces = ClassUtils.getAllInterfaces(beanClass);
260: if (interfaces != null) {
261: if (interfaces.contains(Initialisable.class)) {
262: builder.setInitMethodName(Initialisable.PHASE_NAME);
263: }
264:
265: if (interfaces.contains(Disposable.class)) {
266: builder.setDestroyMethodName(Disposable.PHASE_NAME);
267: }
268: }
269:
270: if (parserContext.isNested()) {
271: // Inner bean definition must receive same singleton status as containing bean.
272: builder.setSingleton(parserContext
273: .getContainingBeanDefinition().isSingleton());
274: }
275:
276: doParse(element, parserContext, builder);
277: return builder.getBeanDefinition();
278: }
279:
280: protected void setRegistry(BeanDefinitionRegistry registry) {
281: this .registry = registry;
282: }
283:
284: protected BeanDefinitionRegistry getRegistry() {
285: if (null == registry) {
286: throw new IllegalStateException(
287: "Set the registry from within doParse");
288: }
289: return registry;
290: }
291:
292: protected void checkElementNameUnique(Element element) {
293: if (null != element.getAttributeNode(ATTRIBUTE_NAME)) {
294: String name = element.getAttribute(ATTRIBUTE_NAME);
295: if (getRegistry().containsBeanDefinition(name)) {
296: throw new IllegalArgumentException("A service named "
297: + name + " already exists.");
298: }
299: }
300: }
301:
302: protected BeanDefinitionBuilder createBeanDefinitionBuilder(
303: Element element, Class beanClass) {
304: return BeanDefinitionBuilder.rootBeanDefinition(beanClass);
305: }
306:
307: protected Class getClassInternal(Element element) {
308: Class beanClass = null;
309: if (isAllowClassAttribute()) {
310: beanClass = getBeanClassFromAttribute(element);
311: }
312: if (beanClass == null) {
313: beanClass = getBeanClass(element);
314: }
315: if (null != beanClass && null != classConstraint
316: && !classConstraint.isAssignableFrom(beanClass)) {
317: throw new IllegalStateException(beanClass
318: + " not a subclass of " + classConstraint + " for "
319: + XMLUtils.elementToString(element));
320: }
321: if (null == beanClass) {
322: throw new IllegalStateException("No class for element "
323: + XMLUtils.elementToString(element));
324: }
325: return beanClass;
326: }
327:
328: /**
329: * Determine the bean class corresponding to the supplied {@link Element} based on an
330: * explicit "class" attribute.
331: *
332: * @param element the <code>Element</code> that is being parsed
333: * @return the {@link Class} of the bean that is being defined via parsing the supplied <code>Element</code>
334: * (must <b>not</b> be <code>null</code>)
335: * @see #parseInternal(org.w3c.dom.Element,ParserContext)
336: */
337: protected Class getBeanClassFromAttribute(Element element) {
338: String className = element.getAttribute(ATTRIBUTE_CLASS);
339: Class clazz = null;
340: if (org.mule.util.StringUtils.isNotBlank(className)) {
341: try {
342: element.removeAttribute(ATTRIBUTE_CLASS);
343: //RM* Todo probably need to use OSGi Loader here
344: clazz = ClassUtils.loadClass(className, getClass());
345: } catch (ClassNotFoundException e) {
346: logger.error("could not load class: " + className, e);
347: }
348: }
349: return clazz;
350: }
351:
352: /**
353: * Determine the bean class corresponding to the supplied {@link Element}.
354: *
355: * @param element the <code>Element</code> that is being parsed
356: * @return the {@link Class} of the bean that is being defined via parsing the supplied <code>Element</code>
357: * (must <b>not</b> be <code>null</code>)
358: * @see #parseInternal(org.w3c.dom.Element,ParserContext)
359: */
360: protected abstract Class getBeanClass(Element element);
361:
362: /**
363: * Parse the supplied {@link Element} and populate the supplied
364: * {@link BeanDefinitionBuilder} as required.
365: * <p>The default implementation delegates to the <code>doParse</code>
366: * version without ParserContext argument.
367: *
368: * @param element the XML element being parsed
369: * @param parserContext the object encapsulating the current state of the parsing process
370: * @param builder used to define the <code>BeanDefinition</code>
371: */
372: protected void doParse(Element element,
373: ParserContext parserContext, BeanDefinitionBuilder builder) {
374: BeanAssembler assembler = getBeanAssembler(element, builder);
375: NamedNodeMap attributes = element.getAttributes();
376: for (int x = 0; x < attributes.getLength(); x++) {
377: Attr attribute = (Attr) attributes.item(x);
378: processProperty(attribute, assembler);
379: }
380: postProcess(getParserContext(), assembler, element);
381: }
382:
383: //@Override
384: protected String resolveId(Element element,
385: AbstractBeanDefinition definition,
386: ParserContext parserContext)
387: throws BeanDefinitionStoreException {
388: return getBeanName(element);
389: }
390:
391: protected boolean isSingleton() {
392: return singleton;
393: }
394:
395: /**
396: * Restricted use - does not include a target.
397: * If possible, use {@link org.mule.config.spring.parsers.AbstractHierarchicalDefinitionParser#getBeanAssembler(org.w3c.dom.Element, org.springframework.beans.factory.support.BeanDefinitionBuilder)}
398: *
399: * @param bean The bean being constructed
400: * @return An assembler that automates Mule-specific logic for bean construction
401: */
402: protected BeanAssembler getBeanAssembler(Element element,
403: BeanDefinitionBuilder bean) {
404: return getBeanAssemblerFactory().newBeanAssembler(
405: beanPropertyConfiguration, bean,
406: beanPropertyConfiguration, null);
407: }
408:
409: protected boolean isAllowClassAttribute() {
410: return allowClassAttribute;
411: }
412:
413: protected void setAllowClassAttribute(boolean allowClassAttribute) {
414: this .allowClassAttribute = allowClassAttribute;
415: }
416:
417: protected Class getClassConstraint() {
418: return classConstraint;
419: }
420:
421: protected void setClassConstraint(Class classConstraint) {
422: this .classConstraint = classConstraint;
423: }
424:
425: protected ParserContext getParserContext() {
426: return parserContext;
427: }
428:
429: protected void setParserContext(ParserContext parserContext) {
430: this .parserContext = parserContext;
431: }
432:
433: /**
434: * @param element The element to test
435: * @return true if the element's parent is <mule> or similar
436: */
437: protected boolean isTopLevel(Element element) {
438: return element.getParentNode().getLocalName().equals(
439: ROOT_ELEMENT);
440: }
441:
442: public AbstractBeanDefinition muleParse(Element element,
443: ParserContext parserContext) {
444: return parseInternal(element, parserContext);
445: }
446:
447: public MuleDefinitionParserConfiguration registerPreProcessor(
448: PreProcessor preProcessor) {
449: preProcessors.addFirst(preProcessor);
450: return this ;
451: }
452:
453: public MuleDefinitionParserConfiguration registerPostProcessor(
454: PostProcessor postProcessor) {
455: postProcessors.add(postProcessor);
456: return this ;
457: }
458:
459: public BeanAssemblerFactory getBeanAssemblerFactory() {
460: return beanAssemblerFactory;
461: }
462:
463: public void setBeanAssemblerFactory(
464: BeanAssemblerFactory beanAssemblerFactory) {
465: this .beanAssemblerFactory = beanAssemblerFactory;
466: }
467:
468: public String getBeanName(Element element) {
469: return AutoIdUtils.getUniqueName(element, "mule-bean");
470: }
471:
472: public MuleDefinitionParserConfiguration addBeanFlag(String flag) {
473: beanAttributes.add(flag);
474: return this;
475: }
476:
477: }
|