| /****************************************************************************** |
| * Copyright (c) 2006, 2010 VMware Inc., Oracle 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. |
| * Oracle Inc. |
| *****************************************************************************/ |
| |
| package org.eclipse.gemini.blueprint.config.internal; |
| |
| import java.util.Collection; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.gemini.blueprint.config.internal.adapter.OsgiServiceRegistrationListenerAdapter; |
| import org.eclipse.gemini.blueprint.config.internal.util.AttributeCallback; |
| import org.eclipse.gemini.blueprint.config.internal.util.ParserUtils; |
| import org.eclipse.gemini.blueprint.service.exporter.support.DefaultInterfaceDetector; |
| import org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean; |
| import org.springframework.beans.MutablePropertyValues; |
| import org.springframework.beans.factory.BeanDefinitionStoreException; |
| import org.springframework.beans.factory.config.BeanDefinition; |
| 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.BeanDefinitionRegistry; |
| import org.springframework.beans.factory.support.ManagedList; |
| import org.springframework.beans.factory.support.RootBeanDefinition; |
| import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; |
| import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; |
| import org.springframework.beans.factory.xml.ParserContext; |
| import org.springframework.core.Conventions; |
| 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; |
| |
| /** |
| * BeanDefinitionParser for service element found in the osgi namespace. |
| * |
| * @author Costin Leau |
| * @author Hal Hildebrand |
| * @author Andy Piper |
| */ |
| public class ServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { |
| |
| class ServiceAttributeCallback implements AttributeCallback { |
| |
| public boolean process(Element parent, Attr attribute, BeanDefinitionBuilder bldr) { |
| String name = attribute.getLocalName(); |
| |
| if (INTERFACE.equals(name)) { |
| bldr.addPropertyValue(INTERFACES_PROP, attribute.getValue()); |
| return false; |
| } else if (REF.equals(name)) { |
| return false; |
| } |
| |
| else if (AUTOEXPORT.equals(name)) { |
| // convert constant to upper case to let Spring do the |
| // conversion |
| String label = attribute.getValue().toUpperCase(Locale.ENGLISH).replace('-', '_'); |
| bldr.addPropertyValue(AUTOEXPORT_PROP, Enum.valueOf(DefaultInterfaceDetector.class, label)); |
| return false; |
| } |
| |
| else if (CONTEXT_CLASSLOADER.equals(name)) { |
| // convert constant to upper case to let Spring do the |
| // conversion |
| |
| String value = attribute.getValue().toUpperCase(Locale.ENGLISH).replace('-', '_'); |
| bldr.addPropertyValue(CCL_PROP, value); |
| return false; |
| } |
| |
| return true; |
| } |
| } |
| |
| // bean properties |
| private static final String TARGET_BEAN_NAME_PROP = "targetBeanName"; |
| |
| private static final String TARGET_PROP = "target"; |
| |
| private static final String LISTENERS_PROP = "listeners"; |
| |
| private static final String INTERFACES_PROP = "interfaces"; |
| |
| private static final String AUTOEXPORT_PROP = "interfaceDetector"; |
| |
| private static final String CCL_PROP = "exportContextClassLoader"; |
| // XML elements |
| private static final String INTERFACES_ID = "interfaces"; |
| |
| private static final String INTERFACE = "interface"; |
| |
| private static final String PROPS_ID = "service-properties"; |
| |
| private static final String LISTENER = "registration-listener"; |
| |
| private static final String REF = "ref"; |
| |
| private static final String AUTOEXPORT = "auto-export"; |
| |
| private static final String CONTEXT_CLASSLOADER = "context-class-loader"; |
| |
| protected Class getBeanClass(Element element) { |
| return OsgiServiceFactoryBean.class; |
| } |
| |
| protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { |
| builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); |
| builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); |
| |
| OsgiDefaultsDefinition defaults = resolveDefaults(element.getOwnerDocument(), parserContext); |
| |
| applyDefaults(parserContext, defaults, builder); |
| |
| AttributeCallback callback = new ServiceAttributeCallback(); |
| parseAttributes(element, builder, new AttributeCallback[] { callback }, defaults); |
| |
| // determine nested/referred beans |
| Object target = null; |
| if (element.hasAttribute(REF)) |
| target = new RuntimeBeanReference(element.getAttribute(REF)); |
| |
| // element is considered parent |
| NodeList nl = element.getChildNodes(); |
| |
| ManagedList listeners = new ManagedList(); |
| |
| // parse all sub elements |
| // we iterate through them since we have to 'catch' the possible nested |
| // bean which has an unknown name local name |
| |
| for (int i = 0; i < nl.getLength(); i++) { |
| Node node = nl.item(i); |
| if (node instanceof Element) { |
| Element subElement = (Element) node; |
| String name = subElement.getLocalName(); |
| |
| // osgi:interface |
| if (parseInterfaces(element, subElement, parserContext, builder)) |
| ; |
| // osgi:service-properties |
| else if (parseServiceProperties(element, subElement, parserContext, builder)) |
| ; |
| // osgi:registration-listener |
| else if (LISTENER.equals(name)) { |
| BeanDefinition listenerDef = parseListener(parserContext, subElement, builder); |
| postProcessListenerDefinition(listenerDef); |
| listeners.add(listenerDef); |
| } else if (BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT.equals(name)) { |
| builder.getRawBeanDefinition().setDescription(subElement.getTextContent()); |
| } |
| |
| // nested bean reference/declaration |
| else |
| target = parseBeanReference(element, subElement, parserContext, builder); |
| } |
| } |
| |
| // if we have a named bean use target_bean_name (so we postpone the service creation) |
| if (target instanceof RuntimeBeanReference) { |
| builder.addPropertyValue(TARGET_BEAN_NAME_PROP, ((RuntimeBeanReference) target).getBeanName()); |
| } else { |
| // add target (can be either an object instance or a bean |
| // definition) |
| builder.addPropertyValue(TARGET_PROP, target); |
| } |
| |
| // add listeners |
| builder.addPropertyValue(LISTENERS_PROP, listeners); |
| } |
| |
| protected void applyDefaults(ParserContext parserContext, OsgiDefaultsDefinition defaults, |
| BeanDefinitionBuilder builder) { |
| if (parserContext.isDefaultLazyInit()) { |
| // Default-lazy-init applies to custom bean definitions as well. |
| builder.setLazyInit(true); |
| } |
| } |
| |
| protected void parseAttributes(Element element, BeanDefinitionBuilder builder, AttributeCallback[] callbacks, |
| OsgiDefaultsDefinition defaults) { |
| |
| // parse attributes |
| ParserUtils.parseCustomAttributes(element, builder, callbacks); |
| } |
| |
| /** |
| * 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 void postProcessListenerDefinition(BeanDefinition wrapperDef) { |
| } |
| |
| // osgi:interfaces |
| private boolean parseInterfaces(Element parent, Element element, ParserContext parserContext, |
| BeanDefinitionBuilder builder) { |
| String name = element.getLocalName(); |
| |
| // osgi:interfaces |
| if (INTERFACES_ID.equals(name)) { |
| // 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); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // osgi:service-properties |
| private boolean parseServiceProperties(Element parent, Element element, ParserContext parserContext, |
| BeanDefinitionBuilder builder) { |
| String name = element.getLocalName(); |
| |
| if (PROPS_ID.equals(name)) { |
| Object props = null; |
| // check inlined ref |
| String ref = element.getAttribute(REF).trim(); |
| |
| boolean hasRef = StringUtils.hasText(ref); |
| |
| if (DomUtils.getChildElementsByTagName(element, BeanDefinitionParserDelegate.ENTRY_ELEMENT).size() > 0) { |
| if (hasRef) { |
| parserContext.getReaderContext().error( |
| "Nested service properties definition cannot be used when attribute 'ref' is specified", |
| element); |
| } else { |
| props = parsePropertyMapElement(parserContext, element, builder.getRawBeanDefinition()); |
| } |
| } |
| |
| if (hasRef) { |
| props = new RuntimeBeanReference(ref); |
| } |
| |
| if (props != null) { |
| builder.addPropertyValue(Conventions.attributeNameToPropertyName(PROPS_ID), props); |
| } else { |
| parserContext.getReaderContext().error("Invalid service property declaration", element); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| // parse nested bean definition |
| private Object parseBeanReference(Element parent, Element element, ParserContext parserContext, |
| BeanDefinitionBuilder builder) { |
| // check shortcut on the parent |
| if (parent.hasAttribute(REF)) |
| parserContext.getReaderContext().error( |
| "nested bean definition/reference cannot be used when attribute 'ref' is specified", parent); |
| return parsePropertySubElement(parserContext, element, builder.getBeanDefinition()); |
| } |
| |
| // osgi:listener |
| protected BeanDefinition parseListener(ParserContext context, Element element, BeanDefinitionBuilder builder) { |
| |
| // filter elements |
| NodeList nl = element.getChildNodes(); |
| |
| // wrapped object |
| Object target = null; |
| // target bean name (used for cycles) |
| String targetName = null; |
| |
| // discover if we have listener with ref and nested bean declaration |
| for (int i = 0; i < nl.getLength(); i++) { |
| Node node = nl.item(i); |
| if (node instanceof Element) { |
| Element nestedDefinition = (Element) node; |
| // check shortcut on the parent |
| if (element.hasAttribute(REF)) |
| context.getReaderContext().error( |
| "nested bean declaration is not allowed if 'ref' attribute has been specified", |
| nestedDefinition); |
| |
| target = parsePropertySubElement(context, nestedDefinition, builder.getBeanDefinition()); |
| // if this is a bean reference (nested <ref>), extract the name |
| if (target instanceof RuntimeBeanReference) { |
| targetName = ((RuntimeBeanReference) target).getBeanName(); |
| } |
| } |
| } |
| |
| // extract registration/unregistration attributes from |
| // <osgi:registration-listener> |
| MutablePropertyValues vals = new MutablePropertyValues(); |
| |
| NamedNodeMap attrs = element.getAttributes(); |
| for (int x = 0; x < attrs.getLength(); x++) { |
| Attr attribute = (Attr) attrs.item(x); |
| String name = attribute.getLocalName(); |
| |
| if (REF.equals(name)) |
| targetName = attribute.getValue(); |
| else { |
| vals.addPropertyValue(Conventions.attributeNameToPropertyName(name), attribute.getValue()); |
| } |
| |
| } |
| |
| // create serviceListener wrapper |
| RootBeanDefinition wrapperDef = new RootBeanDefinition(OsgiServiceRegistrationListenerAdapter.class); |
| |
| // set the target name (if we have one) |
| if (targetName != null) { |
| vals.addPropertyValue(TARGET_BEAN_NAME_PROP, targetName); |
| } |
| // else set the actual target |
| else { |
| vals.addPropertyValue(TARGET_PROP, target); |
| } |
| |
| wrapperDef.setPropertyValues(vals); |
| |
| return wrapperDef; |
| |
| } |
| |
| protected boolean shouldGenerateIdAsFallback() { |
| return true; |
| } |
| |
| 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 Map parsePropertyMapElement(ParserContext context, Element beanDef, BeanDefinition beanDefinition) { |
| return context.getDelegate().parseMapElement(beanDef, beanDefinition); |
| } |
| |
| protected void validateServiceReferences(Element element, String serviceId, ParserContext parserContext) { |
| BeanDefinitionRegistry registry = parserContext.getRegistry(); |
| String[] names = registry.getBeanDefinitionNames(); |
| |
| for (String name : names) { |
| BeanDefinition definition = registry.getBeanDefinition(name); |
| Collection<String> exporters = |
| (Collection<String>) definition.getAttribute(ParserUtils.REFERENCE_LISTENER_REF_ATTR); |
| |
| if (exporters != null && exporters.contains(serviceId)) { |
| parserContext.getReaderContext() |
| .error( |
| "Service exporter '" + serviceId + "' cannot be used as a reference listener by '" |
| + name + "'", element); |
| } |
| } |
| } |
| |
| @Override |
| protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) |
| throws BeanDefinitionStoreException { |
| |
| String id = super.resolveId(element, definition, parserContext); |
| validateServiceReferences(element, id, parserContext); |
| return id; |
| } |
| } |