/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.config.internal; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Set; import org.eclipse.gemini.blueprint.config.internal.adapter.OsgiServiceLifecycleListenerAdapter; import org.eclipse.gemini.blueprint.config.internal.util.AttributeCallback; import org.eclipse.gemini.blueprint.config.internal.util.ParserUtils; import org.eclipse.gemini.blueprint.config.internal.util.ReferenceParsingUtil; import org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean; import org.eclipse.gemini.blueprint.service.importer.support.Availability; import org.eclipse.gemini.blueprint.service.importer.support.ImportContextClassLoaderEnum; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanReferenceFactoryBean; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.Conventions; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Base class for parsing reference declarations. Contains common functionality such as adding listeners (and their * custom methods), interfaces, cardinality and so on. * * <p/> * * <strong>Note:</strong> This parser also handles the cyclic injection between an importer and its listeners by * breaking the chain by creating an adapter instead of the listener. The adapter will then do dependency lookup for the * listener. * * @author Costin Leau * */ public abstract class AbstractReferenceDefinitionParser extends AbstractBeanDefinitionParser { /** * Attribute callback dealing with 'cardinality' attribute. * * @author Costin Leau */ class ReferenceAttributesCallback implements AttributeCallback { public boolean process(Element parent, Attr attribute, BeanDefinitionBuilder builder) { String name = attribute.getLocalName(); String value = attribute.getValue().trim(); if (CARDINALITY.equals(name)) { builder.addPropertyValue(AVAILABILITY_PROP, ReferenceParsingUtil .determineAvailabilityFromCardinality(value)); return false; } if (AVAILABILITY.equals(name)) { Availability avail = ReferenceParsingUtil.determineAvailability(value); builder.addPropertyValue(AVAILABILITY_PROP, avail); return false; } else if (SERVICE_BEAN_NAME.equals(name)) { builder.addPropertyValue(SERVICE_BEAN_NAME_PROP, value); return false; } else if (INTERFACE.equals(name)) { builder.addPropertyValue(INTERFACES_PROP, value); return false; } else if (CONTEXT_CLASSLOADER.equals(name)) { // convert constant to upper case to let Spring do the // conversion String val = value.toUpperCase(Locale.ENGLISH).replace('-', '_'); builder.addPropertyValue(CCL_PROP, ImportContextClassLoaderEnum.valueOf(val)); return false; } return true; } }; // Class properties private static final String LISTENERS_PROP = "listeners"; private static final String AVAILABILITY_PROP = "availability"; private static final String SERVICE_BEAN_NAME_PROP = "serviceBeanName"; private static final String INTERFACES_PROP = "interfaces"; private static final String CCL_PROP = "importContextClassLoader"; private static final String TARGET_BEAN_NAME_PROP = "targetBeanName"; private static final String TARGET_PROP = "target"; // XML attributes/elements private static final String LISTENER = "listener"; private static final String REFERENCE_LISTENER = "reference-listener"; private static final String REF = "ref"; private static final String INTERFACE = "interface"; private static final String INTERFACES = "interfaces"; private static final String AVAILABILITY = "availability"; private static final String CARDINALITY = "cardinality"; private static final String SERVICE_BEAN_NAME = "bean-name"; private static final String CONTEXT_CLASSLOADER = "context-class-loader"; public static final String GENERATED_REF = "org.eclipse.gemini.blueprint.config.reference.generated"; public static final String PROMOTED_REF = "org.eclipse.gemini.blueprint.config.reference.promoted"; /** * Get OSGi defaults (in case they haven't been resolved). * * @param document * @return */ protected OsgiDefaultsDefinition resolveDefaults(Document document, ParserContext parserContext) { return new OsgiDefaultsDefinition(document, parserContext); } protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); Class<?> beanClass = getBeanClass(element); Assert.notNull(beanClass); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); OsgiDefaultsDefinition defaults = resolveDefaults(element.getOwnerDocument(), parserContext); applyDefaults(parserContext, defaults, builder); doParse(element, parserContext, builder); AbstractBeanDefinition def = builder.getBeanDefinition(); // check whether the bean is mandatory (and if it is, make it top-level // bean) if (parserContext.isNested()) { String value = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE); value = (StringUtils.hasText(value) ? value + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR : ""); String generatedName = generateBeanName(value, def, parserContext); // make the bean lazy (since it is an inner bean initiallly) def.setLazyInit(true); // disable autowiring for promoted bean def.setAutowireCandidate(false); def.setAttribute(PROMOTED_REF, Boolean.TRUE); BeanDefinitionHolder holder = new BeanDefinitionHolder(def, generatedName); BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry()); return createBeanReferenceDefinition(generatedName, def); } return def; } protected void applyDefaults(ParserContext parserContext, OsgiDefaultsDefinition defaults, BeanDefinitionBuilder builder) { if (parserContext.isNested()) { // Inner bean definition must receive same scope as containing bean. builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. builder.setLazyInit(true); } } private AbstractBeanDefinition createBeanReferenceDefinition(String beanName, BeanDefinition actualDef) { GenericBeanDefinition def = new GenericBeanDefinition(); def.setBeanClass(BeanReferenceFactoryBean.class); def.setAttribute(GENERATED_REF, Boolean.TRUE); def.setOriginatingBeanDefinition(actualDef); def.setDependsOn(new String[] { beanName }); def.setSynthetic(true); MutablePropertyValues mpv = new MutablePropertyValues(); mpv.addPropertyValue(TARGET_BEAN_NAME_PROP, beanName); def.setPropertyValues(mpv); return def; } protected void doParse(Element element, ParserContext context, BeanDefinitionBuilder builder) { ReferenceParsingUtil.checkAvailabilityAndCardinalityDuplication(element, AVAILABILITY, CARDINALITY, context); OsgiDefaultsDefinition defaults = resolveDefaults(element.getOwnerDocument(), context); AttributeCallback callback = new ReferenceAttributesCallback(); parseAttributes(element, builder, new AttributeCallback[] { callback }, defaults); if (!isCardinalitySpecified(builder)) { applyDefaultCardinality(builder, defaults); } parseNestedElements(element, context, builder); handleNestedDefinition(element, context, builder); } private boolean isCardinalitySpecified(BeanDefinitionBuilder builder) { return (builder.getBeanDefinition().getPropertyValues().getPropertyValue(AVAILABILITY_PROP) != null); } /** * If the reference is a nested bean, make it a top-level bean if it's a mandatory dependency. This is done so that * the beans can be discovered at startup and the appCtx can start waiting. * * @param element * @param context * @param builder */ protected void handleNestedDefinition(Element element, ParserContext context, BeanDefinitionBuilder builder) { } /** * Allow subclasses to add their own callbacks. * * @param element * @param builder * @param callbacks */ protected void parseAttributes(Element element, BeanDefinitionBuilder builder, AttributeCallback[] callbacks, OsgiDefaultsDefinition defaults) { ParserUtils.parseCustomAttributes(element, builder, callbacks); } /** * Indicate the bean definition class for this element. * * @param element * @return */ protected abstract Class getBeanClass(Element element); /** * Apply default cardinality. * * @param builder * @param defaults */ protected void applyDefaultCardinality(BeanDefinitionBuilder builder, OsgiDefaultsDefinition defaults) { builder.addPropertyValue(AVAILABILITY_PROP, defaults.getAvailability()); } /** * Parse nested elements. In case of a reference definition, this means using the listeners. * * * @param element * @param context * @param builder */ protected void parseNestedElements(Element element, ParserContext context, BeanDefinitionBuilder builder) { parseInterfaces(element, context, builder); parseListeners(element, getListenerElementName(), context, builder); // deprecated listener parseListeners(element, LISTENER, context, builder); } protected String getListenerElementName() { return REFERENCE_LISTENER; } /** * Parse interfaces. * * @param element * @param context * @param builder */ protected void parseInterfaces(Element parent, ParserContext parserContext, BeanDefinitionBuilder builder) { Element element = DomUtils.getChildElementByTagName(parent, INTERFACES); if (element != null) { // check shortcut on the parent if (parent.hasAttribute(INTERFACE)) { parserContext.getReaderContext().error( "either 'interface' attribute or <intefaces> sub-element has be specified", parent); } Set interfaces = parsePropertySetElement(parserContext, element, builder.getBeanDefinition()); builder.addPropertyValue(INTERFACES_PROP, interfaces); } } /** * Parse listeners. * * @param element * @param context * @param builder */ protected void parseListeners(Element element, String subElementName, ParserContext context, BeanDefinitionBuilder builder) { List listeners = DomUtils.getChildElementsByTagName(element, subElementName); ManagedList listenersRef = new ManagedList(); // loop on listeners for (Iterator iter = listeners.iterator(); iter.hasNext();) { Element listnr = (Element) iter.next(); // wrapper target object Object target = null; // target bean name (in case of a reference) String targetName = null; // filter elements NodeList nl = listnr.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element beanDef = (Element) node; // check inline ref if (listnr.hasAttribute(REF)) context.getReaderContext() .error("nested bean declaration is not allowed if 'ref' attribute has been specified", beanDef); target = parsePropertySubElement(context, beanDef, builder.getBeanDefinition()); // if this is a bean reference (nested <ref>), extract the name if (target instanceof RuntimeBeanReference) { targetName = ((RuntimeBeanReference) target).getBeanName(); } } } // extract bind/unbind attributes from <osgi:listener> // Element MutablePropertyValues vals = new MutablePropertyValues(); NamedNodeMap attrs = listnr.getAttributes(); for (int x = 0; x < attrs.getLength(); x++) { Attr attribute = (Attr) attrs.item(x); String name = attribute.getLocalName(); // extract ref value if (REF.equals(name)) targetName = attribute.getValue(); else vals.addPropertyValue(Conventions.attributeNameToPropertyName(name), attribute.getValue()); } // create serviceListener adapter RootBeanDefinition wrapperDef = new RootBeanDefinition(OsgiServiceLifecycleListenerAdapter.class); // set the target name (if we have one) if (targetName != null) { // do some validation BeanDefinitionRegistry registry = context.getRegistry(); if (registry.containsBeanDefinition(targetName)) { BeanDefinition beanDefinition = registry.getBeanDefinition(targetName); if (beanDefinition.getBeanClassName().equals(OsgiServiceFactoryBean.class.getName())) { context.getReaderContext() .error("service exporter '" + targetName + "' cannot be used as a reference listener", element); } } // no validation could be performed, save the name for easier retrieval AbstractBeanDefinition bd = builder.getBeanDefinition(); Collection<String> str = (Collection<String>) bd.getAttribute(ParserUtils.REFERENCE_LISTENER_REF_ATTR); if (str == null) { str = new LinkedHashSet<String>(2); bd.setAttribute(ParserUtils.REFERENCE_LISTENER_REF_ATTR, str); } str.add(targetName); vals.addPropertyValue(TARGET_BEAN_NAME_PROP, targetName); } // else set the actual target else vals.addPropertyValue(TARGET_PROP, target); wrapperDef.setPropertyValues(vals); wrapperDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); postProcessListenerDefinition(wrapperDef); // add listener to list listenersRef.add(wrapperDef); } PropertyValue previousListener = builder.getRawBeanDefinition().getPropertyValues().getPropertyValue(LISTENERS_PROP); if (previousListener != null) { ManagedList ml = (ManagedList) previousListener.getValue(); listenersRef.addAll(0, ml); } builder.addPropertyValue(LISTENERS_PROP, listenersRef); } protected void postProcessListenerDefinition(BeanDefinition wrapperDef) { } protected Object parsePropertySubElement(ParserContext context, Element beanDef, BeanDefinition beanDefinition) { return context.getDelegate().parsePropertySubElement(beanDef, beanDefinition); } protected Set parsePropertySetElement(ParserContext context, Element beanDef, BeanDefinition beanDefinition) { return context.getDelegate().parseSetElement(beanDef, beanDefinition); } protected String generateBeanName(String prefix, BeanDefinition def, ParserContext parserContext) { BeanDefinitionRegistry registry = parserContext.getRegistry(); String name = prefix + BeanDefinitionReaderUtils.generateBeanName(def, registry); String generated = name; int counter = 0; while (registry.containsBeanDefinition(generated)) { generated = name + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + counter; if (parserContext.isNested()) { generated = generated.concat("#generated"); } counter++; } return generated; } }