blob: 24d5fc61ced99a104f9496853e45c6985d4ec0bc [file] [log] [blame]
/******************************************************************************
* 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.eclipse.gemini.blueprint.util.BeanReferenceFactoryBean;
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.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;
}
}