content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.aries.blueprint.compendium.cm;
  
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Properties;
  import java.util.Set;
  
  import org.apache.aries.blueprint.ComponentDefinitionRegistry;
  import org.apache.aries.blueprint.NamespaceHandler;
  import org.apache.aries.blueprint.ParserContext;
  import org.apache.aries.blueprint.ext.PlaceholdersUtils;
  import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
  import org.apache.aries.blueprint.mutable.MutableCollectionMetadata;
  import org.apache.aries.blueprint.mutable.MutableComponentMetadata;
  import org.apache.aries.blueprint.mutable.MutableIdRefMetadata;
  import org.apache.aries.blueprint.mutable.MutableMapMetadata;
  import org.apache.aries.blueprint.mutable.MutableRefMetadata;
  import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
  import org.apache.aries.blueprint.mutable.MutableValueMetadata;
  import org.apache.aries.blueprint.utils.ServiceListener;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.FrameworkUtil;
  import org.osgi.service.blueprint.container.ComponentDefinitionException;
  import org.osgi.service.blueprint.reflect.BeanMetadata;
  import org.osgi.service.blueprint.reflect.BeanProperty;
  import org.osgi.service.blueprint.reflect.CollectionMetadata;
  import org.osgi.service.blueprint.reflect.ComponentMetadata;
  import org.osgi.service.blueprint.reflect.IdRefMetadata;
  import org.osgi.service.blueprint.reflect.MapMetadata;
  import org.osgi.service.blueprint.reflect.Metadata;
  import org.osgi.service.blueprint.reflect.RefMetadata;
  import org.osgi.service.blueprint.reflect.ReferenceMetadata;
  import org.osgi.service.blueprint.reflect.RegistrationListener;
  import org.osgi.service.blueprint.reflect.ServiceMetadata;
  import org.osgi.service.blueprint.reflect.ValueMetadata;
  import org.osgi.service.cm.ConfigurationAdmin;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.w3c.dom.CharacterData;
  import org.w3c.dom.Comment;
  import org.w3c.dom.Element;
  import org.w3c.dom.EntityReference;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  /**
   * Namespace handler for the Config Admin service.
   * This handler will parse the various elements defined and populate / modify the registry
   * accordingly.
   *
   * @see CmManagedProperties
   * @see CmManagedServiceFactory
   * @see CmProperties
   * @see CmPropertyPlaceholder
   *
   * @version $Rev$, $Date$
   */
  public class CmNamespaceHandler implements NamespaceHandler {
  
      public static final String BLUEPRINT_NAMESPACE = "http://www.osgi.org/xmlns/blueprint/v1.0.0";
      public static final String BLUEPRINT_CM_NAMESPACE_1_0 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0";
      public static final String BLUEPRINT_CM_NAMESPACE_1_1 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0";
      public static final String BLUEPRINT_CM_NAMESPACE_1_2 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0";
      public static final String BLUEPRINT_CM_NAMESPACE_1_3 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.3.0";
      public static final String BLUEPRINT_CM_NAMESPACE_1_4 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.4.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_0 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_1 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_2 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_3 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_4 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_5 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0";
      public static final String BLUEPRINT_EXT_NAMESPACE_V1_6 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.6.0";
  
      public static final String PROPERTY_PLACEHOLDER_ELEMENT = "property-placeholder";
      public static final String MANAGED_PROPERTIES_ELEMENT = "managed-properties";
      public static final String MANAGED_SERVICE_FACTORY_ELEMENT = "managed-service-factory";
      public static final String CM_PROPERTIES_ELEMENT = "cm-properties";
      public static final String DEFAULT_PROPERTIES_ELEMENT = "default-properties";
      public static final String PROPERTY_ELEMENT = "property";
      public static final String INTERFACES_ELEMENT = "interfaces";
      public static final String VALUE_ELEMENT = "value";
      public static final String MANAGED_COMPONENT_ELEMENT = "managed-component";
      public static final String LOCATION_ELEMENT = "location";
      public static final String SERVICE_PROPERTIES_ELEMENT = "service-properties";
      public static final String REGISTRATION_LISTENER_ELEMENT = "registration-listener";
  
      public static final String ID_ATTRIBUTE = "id";
      public static final String SYSTEM_PROPERTIES_NEVER = "never";
      public static final String PERSISTENT_ID_ATTRIBUTE = "persistent-id";
      public static final String PLACEHOLDER_PREFIX_ATTRIBUTE = "placeholder-prefix";
      public static final String PLACEHOLDER_SUFFIX_ATTRIBUTE = "placeholder-suffix";
      public static final String PLACEHOLDER_NULL_VALUE_ATTRIBUTE = "null-value";
      public static final String DEFAULTS_REF_ATTRIBUTE = "defaults-ref";
      public static final String UPDATE_STRATEGY_ATTRIBUTE = "update-strategy";
      public static final String UPDATE_METHOD_ATTRIBUTE = "update-method";
      public static final String FACTORY_PID_ATTRIBUTE = "factory-pid";
      public static final String AUTO_EXPORT_ATTRIBUTE = "auto-export";
      public static final String RANKING_ATTRIBUTE = "ranking";
      public static final String INTERFACE_ATTRIBUTE = "interface";
      public static final String UPDATE_ATTRIBUTE = "update";
      public static final String SYSTEM_PROPERTIES_ATTRIBUTE = "system-properties";
      public static final String IGNORE_MISSING_LOCATIONS_ATTRIBUTE = "ignore-missing-locations";
  
      public static final String AUTO_EXPORT_DISABLED = "disabled";
      public static final String AUTO_EXPORT_INTERFACES = "interfaces";
      public static final String AUTO_EXPORT_CLASS_HIERARCHY = "class-hierarchy";
      public static final String AUTO_EXPORT_ALL = "all-classes";
      public static final String AUTO_EXPORT_DEFAULT = AUTO_EXPORT_DISABLED;
      public static final String RANKING_DEFAULT = "0";
  
      private static final String MANAGED_OBJECT_MANAGER_NAME = "org.apache.aries.managedObjectManager";
      
      private static final Logger LOGGER = LoggerFactory.getLogger(CmNamespaceHandler.class);
      private static final Set<String> EXT_URIS = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(
                                                      BLUEPRINT_EXT_NAMESPACE_V1_0,
                                                      BLUEPRINT_EXT_NAMESPACE_V1_1,
                                                      BLUEPRINT_EXT_NAMESPACE_V1_2,
                                                      BLUEPRINT_EXT_NAMESPACE_V1_3,
                                                      BLUEPRINT_EXT_NAMESPACE_V1_4,
                                                      BLUEPRINT_EXT_NAMESPACE_V1_5,
                                                      BLUEPRINT_EXT_NAMESPACE_V1_6)));
      private static final Set<String> CM_URIS = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(
                                                      BLUEPRINT_CM_NAMESPACE_1_0,
                                                      BLUEPRINT_CM_NAMESPACE_1_1,
                                                      BLUEPRINT_CM_NAMESPACE_1_2,
                                                      BLUEPRINT_CM_NAMESPACE_1_3,
                                                      BLUEPRINT_CM_NAMESPACE_1_4)));
  
      // This property is static but it should be ok since there will be only a single instance
      // of this class for the bundle
      private static ConfigurationAdmin configAdmin;
  
      private int idCounter;
  
      public int getIdCounter() {
          return idCounter;
      }
  
      public void setIdCounter(int idCounter) {
          this.idCounter = idCounter;
      }
  
      public static ConfigurationAdmin getConfigAdmin() {
          return configAdmin;
      }
  
      public void setConfigAdmin(ConfigurationAdmin configAdmin) {
          CmNamespaceHandler.configAdmin = configAdmin;
      }
  
      public URL getSchemaLocation(String namespace) {
          if (isCmNamespace(namespace)) {
              String v = namespace.substring("http://aries.apache.org/blueprint/xmlns/blueprint-cm/v".length());
              return getClass().getResource("blueprint-cm-" + v + ".xsd");
          } else if (isExtNamespace(namespace)) {
              try {
                  Class<?> extNsHandlerClazz;
                  Bundle extBundle = FrameworkUtil.getBundle(PlaceholdersUtils.class);
                  if (extBundle == null) {
                      // we may not be in OSGi environment
                      extNsHandlerClazz = getClass().getClassLoader().loadClass("org.apache.aries.blueprint.ext.impl.ExtNamespaceHandler");
                  } else {
                      extNsHandlerClazz = extBundle.loadClass("org.apache.aries.blueprint.ext.impl.ExtNamespaceHandler");
                  }
                  return ((NamespaceHandler) extNsHandlerClazz.newInstance()).getSchemaLocation(namespace);
              } catch (Throwable t) {
                  LOGGER.warn("Could not locate ext namespace schema", t);
                  return null;
              }
          } else {
              return null;
          }
      }
  
      public Set<Class> getManagedClasses() {
          return new HashSet<Class>(Arrays.asList(
                  CmPropertyPlaceholder.class,
                  CmManagedServiceFactory.class,
                  CmManagedProperties.class,
                  CmProperties.class
          ));
      }
  
      public Metadata parse(Element element, ParserContext context) {
          LOGGER.debug("Parsing element {{}}{}", element.getNamespaceURI(), element.getLocalName());
          ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
          registerManagedObjectManager(context, registry);
          if (nodeNameEquals(element, PROPERTY_PLACEHOLDER_ELEMENT)) {
              return parsePropertyPlaceholder(context, element);
          } else if (nodeNameEquals(element, MANAGED_SERVICE_FACTORY_ELEMENT)) {
              return parseManagedServiceFactory(context, element);
          } else if (nodeNameEquals(element, CM_PROPERTIES_ELEMENT)) {
              return parseCmProperties(context, element);
          } else {
              throw new ComponentDefinitionException("Unsupported element: " + element.getNodeName());
          }
      }
  
      public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
          LOGGER.debug("Decorating node {{}}{}", node.getNamespaceURI(), node.getLocalName());
          ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
          registerManagedObjectManager(context, registry);
          if (node instanceof Element) {
              if (nodeNameEquals(node, MANAGED_PROPERTIES_ELEMENT)) {
                  return decorateManagedProperties(context, (Element) node, component);
              } else if (nodeNameEquals(node, CM_PROPERTIES_ELEMENT)) {
                  return decorateCmProperties(context, (Element) node, component);
              } else {
                  throw new ComponentDefinitionException("Unsupported element: " + node.getNodeName());
              }
          } else {
              throw new ComponentDefinitionException("Illegal use of blueprint cm namespace");
          }
      }
  
      private ComponentMetadata parseCmProperties(ParserContext context, Element element) {
          String id = getId(context, element);
  
          MutableBeanMetadata factoryMetadata = context.createMetadata(MutableBeanMetadata.class);
          generateIdIfNeeded(context, factoryMetadata);
          factoryMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
          factoryMetadata.setRuntimeClass(CmProperties.class);
          factoryMetadata.setInitMethod("init");
          factoryMetadata.setDestroyMethod("destroy");
          factoryMetadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
          factoryMetadata.addProperty("configAdmin", createConfigurationAdminRef(context));
          factoryMetadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
          String persistentId = element.getAttribute(PERSISTENT_ID_ATTRIBUTE);
          factoryMetadata.addProperty("persistentId", createValue(context, persistentId));
          context.getComponentDefinitionRegistry().registerComponentDefinition(factoryMetadata);
  
          MutableBeanMetadata propertiesMetadata = context.createMetadata(MutableBeanMetadata.class);
          propertiesMetadata.setId(id);
          propertiesMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
          propertiesMetadata.setRuntimeClass(Properties.class);
          propertiesMetadata.setFactoryComponent(createRef(context, factoryMetadata.getId()));
          propertiesMetadata.setFactoryComponent(factoryMetadata);
          propertiesMetadata.setFactoryMethod("getProperties");
          // Work around ARIES-877
          propertiesMetadata.setDependsOn(Arrays.asList(factoryMetadata.getId()));
  
          return propertiesMetadata;
      }
  
      private ComponentMetadata parsePropertyPlaceholder(ParserContext context, Element element) {
          MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
          metadata.setProcessor(true);
          metadata.setId(getId(context, element));
          metadata.setScope(BeanMetadata.SCOPE_SINGLETON);
          metadata.setRuntimeClass(CmPropertyPlaceholder.class);
          metadata.setInitMethod("init");
          metadata.setDestroyMethod("destroy");
          metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
          metadata.addProperty("configAdmin", createConfigurationAdminRef(context));
          metadata.addProperty("persistentId", createValue(context, element.getAttribute(PERSISTENT_ID_ATTRIBUTE)));
          String prefix = element.hasAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE)
                                      ? element.getAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE)
                                      : "${";
          metadata.addProperty("placeholderPrefix", createValue(context, prefix));
          String suffix = element.hasAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE)
                                      ? element.getAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE)
                                      : "}";
          metadata.addProperty("placeholderSuffix", createValue(context, suffix));
          String nullValue = element.hasAttribute(PLACEHOLDER_NULL_VALUE_ATTRIBUTE)
                  ? element.getAttribute(PLACEHOLDER_NULL_VALUE_ATTRIBUTE)
                  : null;
          if (nullValue != null) {
              metadata.addProperty("nullValue", createValue(context, nullValue));
          }
          String defaultsRef = element.hasAttribute(DEFAULTS_REF_ATTRIBUTE) ? element.getAttribute(DEFAULTS_REF_ATTRIBUTE) : null;
          if (defaultsRef != null) {
              metadata.addProperty("defaultProperties", createRef(context, defaultsRef));
          }
          String ignoreMissingLocations = extractIgnoreMissingLocations(element);
          if (ignoreMissingLocations != null) {
              metadata.addProperty("ignoreMissingLocations", createValue(context, ignoreMissingLocations));
          }
          String systemProperties = extractSystemPropertiesAttribute(element);
          if (systemProperties == null) {
              systemProperties = SYSTEM_PROPERTIES_NEVER;
          }
          metadata.addProperty("systemProperties", createValue(context, systemProperties));
          String updateStrategy = element.getAttribute(UPDATE_STRATEGY_ATTRIBUTE);
          if (updateStrategy != null) {
              metadata.addProperty("updateStrategy", createValue(context, updateStrategy));
          }
          metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
          // Parse elements
          List<String> locations = new ArrayList<String>();
          NodeList nl = element.getChildNodes();
          for (int i = 0; i < nl.getLength(); i++) {
              Node node = nl.item(i);
              if (node instanceof Element) {
                  Element e = (Element) node;
                  if (isCmNamespace(e.getNamespaceURI())) {
                      if (nodeNameEquals(e, DEFAULT_PROPERTIES_ELEMENT)) {
                          if (defaultsRef != null) {
                              throw new ComponentDefinitionException("Only one of " + DEFAULTS_REF_ATTRIBUTE + " attribute or " + DEFAULT_PROPERTIES_ELEMENT + " element is allowed");
                          }
                          Metadata props = parseDefaultProperties(context, metadata, e);
                          metadata.addProperty("defaultProperties", props);
                      }
                  } else if (isExtNamespace(e.getNamespaceURI())) {
                      if (nodeNameEquals(e, LOCATION_ELEMENT)) {
                          locations.add(getTextValue(e));
                      }
                  } 
              }
          }
          if (!locations.isEmpty()) {
              metadata.addProperty("locations", createList(context, locations));
          }
  
          PlaceholdersUtils.validatePlaceholder(metadata, context.getComponentDefinitionRegistry());
          
          return metadata;
      }
  
      private String extractSystemPropertiesAttribute(Element element) {
          for (String uri : EXT_URIS) {
              if (element.hasAttributeNS(uri, SYSTEM_PROPERTIES_ATTRIBUTE)) {
                  return element.getAttributeNS(uri, SYSTEM_PROPERTIES_ATTRIBUTE);
              }
          }
          return null;
      }
  
      private String extractIgnoreMissingLocations(Element element) {
          for (String uri : EXT_URIS) {
              if (element.hasAttributeNS(uri, IGNORE_MISSING_LOCATIONS_ATTRIBUTE)) {
                  return element.getAttributeNS(uri, IGNORE_MISSING_LOCATIONS_ATTRIBUTE);
              }
          }
          return null;
      }
  
      private Metadata parseDefaultProperties(ParserContext context, MutableBeanMetadata enclosingComponent, Element element) {
          MutableMapMetadata props = context.createMetadata(MutableMapMetadata.class);
          NodeList nl = element.getChildNodes();
          for (int i = 0; i < nl.getLength(); i++) {
              Node node = nl.item(i);
              if (node instanceof Element) {
                  Element e = (Element) node;
                  if (isCmNamespace(e.getNamespaceURI())) {
                      if (nodeNameEquals(e, PROPERTY_ELEMENT)) {
                          BeanProperty prop = context.parseElement(BeanProperty.class, enclosingComponent, e);
                          props.addEntry(createValue(context, prop.getName(), String.class.getName()), prop.getValue());
                      }
                  }
              }
          }
          return props;
      }
  
      private ComponentMetadata parseManagedServiceFactory(ParserContext context, Element element) {
          String id = getId(context, element);
  
          MutableBeanMetadata factoryMetadata = context.createMetadata(MutableBeanMetadata.class);
          generateIdIfNeeded(context, factoryMetadata);
          factoryMetadata.addProperty("id", createValue(context, factoryMetadata.getId()));
          factoryMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
          factoryMetadata.setRuntimeClass(CmManagedServiceFactory.class);
          factoryMetadata.setInitMethod("init");
          factoryMetadata.setDestroyMethod("destroy");
          factoryMetadata.addArgument(createRef(context, "blueprintContainer"), null, 0);
          factoryMetadata.addProperty("factoryPid", createValue(context, element.getAttribute(FACTORY_PID_ATTRIBUTE)));
          String autoExport = element.hasAttribute(AUTO_EXPORT_ATTRIBUTE) ? element.getAttribute(AUTO_EXPORT_ATTRIBUTE) : AUTO_EXPORT_DEFAULT;
          if (AUTO_EXPORT_DISABLED.equals(autoExport)) {
              autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_DISABLED);
          } else if (AUTO_EXPORT_INTERFACES.equals(autoExport)) {
              autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_INTERFACES);
          } else if (AUTO_EXPORT_CLASS_HIERARCHY.equals(autoExport)) {
              autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_CLASS_HIERARCHY);
          } else if (AUTO_EXPORT_ALL.equals(autoExport)) {
              autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
          } else {
              throw new ComponentDefinitionException("Illegal value (" + autoExport + ") for " + AUTO_EXPORT_ATTRIBUTE + " attribute");
          }
          factoryMetadata.addProperty("autoExport", createValue(context, autoExport));
          String ranking = element.hasAttribute(RANKING_ATTRIBUTE) ? element.getAttribute(RANKING_ATTRIBUTE) : RANKING_DEFAULT;
          factoryMetadata.addProperty("ranking", createValue(context, ranking));
  
          List<String> interfaces = null;
          if (element.hasAttribute(INTERFACE_ATTRIBUTE)) {
              interfaces = Collections.singletonList(element.getAttribute(INTERFACE_ATTRIBUTE));
              factoryMetadata.addProperty("interfaces", createList(context, interfaces));
          }
         
          // Parse elements
          List<RegistrationListener> listeners = new ArrayList<RegistrationListener>();
          NodeList nl = element.getChildNodes();
          for (int i = 0; i < nl.getLength(); i++) {
              Node node = nl.item(i);
              if (node instanceof Element) {
                  Element e = (Element) node;
                  if (isBlueprintNamespace(e.getNamespaceURI())) {
                      if (nodeNameEquals(e, INTERFACES_ELEMENT)) {
                          if (interfaces != null) {
                              throw new ComponentDefinitionException("Only one of " + INTERFACE_ATTRIBUTE + " attribute or " + INTERFACES_ELEMENT + " element must be used");
                          }
                          interfaces = parseInterfaceNames(e);
                          factoryMetadata.addProperty("interfaces", createList(context, interfaces));                    
                      } else if (nodeNameEquals(e, SERVICE_PROPERTIES_ELEMENT)) { 
                          MapMetadata map = context.parseElement(MapMetadata.class,
                              factoryMetadata, e);
                          factoryMetadata.addProperty("serviceProperties", map);
                          NodeList enl = e.getChildNodes();
                          for (int j = 0; j < enl.getLength(); j++) {
                              Node enode = enl.item(j);
                              if (enode instanceof Element) {
                                  if (isCmNamespace(enode.getNamespaceURI()) && nodeNameEquals(enode, CM_PROPERTIES_ELEMENT)) {
                                      decorateCmProperties(context, (Element) enode, factoryMetadata);
                                  }
                              }
                          }
                      } else if (nodeNameEquals(e, REGISTRATION_LISTENER_ELEMENT)) {
                          listeners.add(context.parseElement(RegistrationListener.class,
                              factoryMetadata, e));
                      }
                  } else if (isCmNamespace(e.getNamespaceURI())) {
                      if (nodeNameEquals(e, MANAGED_COMPONENT_ELEMENT)) {
                          MutableBeanMetadata managedComponent = context.parseElement(MutableBeanMetadata.class, null, e);
                          generateIdIfNeeded(context, managedComponent);
                          managedComponent.setScope(BeanMetadata.SCOPE_PROTOTYPE);
                          // destroy-method on managed-component has different signature than on regular beans
                          // so we'll handle it differently
                          String destroyMethod = managedComponent.getDestroyMethod();
                          if (destroyMethod != null) {
                              factoryMetadata.addProperty("componentDestroyMethod", createValue(context, destroyMethod));
                              managedComponent.setDestroyMethod(null);
                          }
                          context.getComponentDefinitionRegistry().registerComponentDefinition(managedComponent);
                          factoryMetadata.addProperty("managedComponentName", createIdRef(context, managedComponent.getId()));
                      }
                  }
              }
          }
  
          MutableCollectionMetadata listenerCollection = context.createMetadata(MutableCollectionMetadata.class);
          listenerCollection.setCollectionClass(List.class);
          for (RegistrationListener listener : listeners) {
              MutableBeanMetadata bean = context.createMetadata(MutableBeanMetadata.class);
              bean.setRuntimeClass(ServiceListener.class);
              bean.addProperty("listener", listener.getListenerComponent());
              bean.addProperty("registerMethod", createValue(context, listener.getRegistrationMethod()));
              bean.addProperty("unregisterMethod", createValue(context, listener.getUnregistrationMethod()));
              listenerCollection.addValue(bean);
          }
          factoryMetadata.addProperty("listeners", listenerCollection);
          
          context.getComponentDefinitionRegistry().registerComponentDefinition(factoryMetadata);
          
          MutableBeanMetadata mapMetadata = context.createMetadata(MutableBeanMetadata.class);
          mapMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
          mapMetadata.setId(id);
          mapMetadata.setFactoryComponent(createRef(context, factoryMetadata.getId()));
          mapMetadata.setFactoryMethod("getServiceMap");
          return mapMetadata;
      }
  
      private ComponentMetadata decorateCmProperties(ParserContext context, Element element, ComponentMetadata component) {
          generateIdIfNeeded(context, ((MutableComponentMetadata) component));
          MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
          metadata.setProcessor(true);
          metadata.setId(getId(context, element));
          metadata.setRuntimeClass(CmProperties.class);
          String persistentId = element.getAttribute(PERSISTENT_ID_ATTRIBUTE);
          // if persistentId is "" the cm-properties element in nested in managed-service-factory
          // and the configuration object will come from the factory. So we only really need to register
          // ManagedService if the persistentId is not an empty string.
          if (persistentId.length() > 0) {
              metadata.setInitMethod("init");
              metadata.setDestroyMethod("destroy");
          }
          metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
          metadata.addProperty("configAdmin", createConfigurationAdminRef(context));
          metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
          metadata.addProperty("persistentId", createValue(context, persistentId));
          if (element.hasAttribute(UPDATE_ATTRIBUTE)) {
              metadata.addProperty("update", createValue(context, element.getAttribute(UPDATE_ATTRIBUTE)));
          }
          metadata.addProperty("serviceId", createIdRef(context, component.getId()));
          context.getComponentDefinitionRegistry().registerComponentDefinition(metadata);
          return component;
      }
  
      private ComponentMetadata decorateManagedProperties(ParserContext context, Element element, ComponentMetadata component) {
          if (!(component instanceof MutableBeanMetadata)) {
              throw new ComponentDefinitionException("Element " + MANAGED_PROPERTIES_ELEMENT + " must be used inside a <bp:bean> element");
          }
          generateIdIfNeeded(context, ((MutableBeanMetadata) component));
          MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
          metadata.setProcessor(true);
          metadata.setId(getId(context, element));
          metadata.setRuntimeClass(CmManagedProperties.class);
          String persistentId = element.getAttribute(PERSISTENT_ID_ATTRIBUTE);
          // if persistentId is "" the managed properties element in nested in managed-service-factory
          // and the configuration object will come from the factory. So we only really need to register
          // ManagedService if the persistentId is not an empty string.
          if (persistentId.length() > 0) {
              metadata.setInitMethod("init");
              metadata.setDestroyMethod("destroy");
          }
          metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
          metadata.addProperty("configAdmin", createConfigurationAdminRef(context));
          metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
          metadata.addProperty("persistentId", createValue(context, persistentId));
          String updateStrategy = element.getAttribute(UPDATE_STRATEGY_ATTRIBUTE);
          if (updateStrategy != null) {
              metadata.addProperty("updateStrategy", createValue(context, updateStrategy));
          }
          if (element.hasAttribute(UPDATE_METHOD_ATTRIBUTE)) {
              metadata.addProperty("updateMethod", createValue(context, element.getAttribute(UPDATE_METHOD_ATTRIBUTE)));
          } else if ("component-managed".equals(updateStrategy)) {
              throw new ComponentDefinitionException(UPDATE_METHOD_ATTRIBUTE + " attribute must be set when " + UPDATE_STRATEGY_ATTRIBUTE + " is set to 'component-managed'");
          }
          metadata.addProperty("beanName", createIdRef(context, component.getId()));
          context.getComponentDefinitionRegistry().registerComponentDefinition(metadata);
          return component;
      }
  
      private void registerManagedObjectManager(ParserContext context, ComponentDefinitionRegistry registry) {
          if (registry.getComponentDefinition(MANAGED_OBJECT_MANAGER_NAME) == null) {
              MutableBeanMetadata beanMetadata = context.createMetadata(MutableBeanMetadata.class);
              beanMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
              beanMetadata.setId(MANAGED_OBJECT_MANAGER_NAME);
              beanMetadata.setRuntimeClass(ManagedObjectManager.class);            
              registry.registerComponentDefinition(beanMetadata);
          }
      }
      
      private MutableReferenceMetadata createConfigurationAdminRef(ParserContext context) {
          return createServiceRef(context, ConfigurationAdmin.class, null);
      }
      
      private static ValueMetadata createValue(ParserContext context, String value) {
          return createValue(context, value, null);
      }
  
      private static ValueMetadata createValue(ParserContext context, String value, String type) {
          MutableValueMetadata m = context.createMetadata(MutableValueMetadata.class);
          m.setStringValue(value);
          m.setType(type);
          return m;
      }
  
      private static RefMetadata createRef(ParserContext context, String value) {
          MutableRefMetadata m = context.createMetadata(MutableRefMetadata.class);
          m.setComponentId(value);
          return m;
      }
      
      private MutableReferenceMetadata createServiceRef(ParserContext context, Class<?> cls, String filter) {
          MutableReferenceMetadata m = context.createMetadata(MutableReferenceMetadata.class);
          m.setRuntimeInterface(cls);
          m.setInterface(cls.getName());
          m.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
          m.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
          
          if (filter != null) {
              m.setFilter(filter);
          }
          
          return m;
      }
  
      private static IdRefMetadata createIdRef(ParserContext context, String value) {
          MutableIdRefMetadata m = context.createMetadata(MutableIdRefMetadata.class);
          m.setComponentId(value);
          return m;
      }
  
      private static CollectionMetadata createList(ParserContext context, List<String> list) {
          MutableCollectionMetadata m = context.createMetadata(MutableCollectionMetadata.class);
          m.setCollectionClass(List.class);
          m.setValueType(String.class.getName());
          for (String v : list) {
              m.addValue(createValue(context, v, String.class.getName()));
          }
          return m;
      }
  
      private static String getTextValue(Element element) {
          StringBuffer value = new StringBuffer();
          NodeList nl = element.getChildNodes();
          for (int i = 0; i < nl.getLength(); i++) {
              Node item = nl.item(i);
              if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
                  value.append(item.getNodeValue());
              }
          }
          return value.toString();
      }
  
      private static boolean nodeNameEquals(Node node, String name) {
          return (name.equals(node.getNodeName()) || name.equals(node.getLocalName()));
      }
  
      public static boolean isBlueprintNamespace(String ns) {
          return BLUEPRINT_NAMESPACE.equals(ns);
      }
  
      public static boolean isCmNamespace(String uri) {
          return CM_URIS.contains(uri);
      }
  
      public static boolean isExtNamespace(String uri) {
          return EXT_URIS.contains(uri);
      }
  
      public String getId(ParserContext context, Element element) {
          if (element.hasAttribute(ID_ATTRIBUTE)) {
              return element.getAttribute(ID_ATTRIBUTE);
          } else {
              return generateId(context);
          }
      }
  
      public void generateIdIfNeeded(ParserContext context, MutableComponentMetadata metadata) {
          if (metadata.getId() == null) {
              metadata.setId(generateId(context));
          }
      }
  
      private String generateId(ParserContext context) {
          String id;
          do {
              id = ".cm-" + ++idCounter;
          } while (context.getComponentDefinitionRegistry().containsComponentDefinition(id));
          return id;
      }
      
      public List<String> parseInterfaceNames(Element element) {
          List<String> interfaceNames = new ArrayList<String>();
          NodeList nl = element.getChildNodes();
          for (int i = 0; i < nl.getLength(); i++) {
              Node node = nl.item(i);
              if (node instanceof Element) {
                  Element e = (Element) node;
                  if (nodeNameEquals(e, VALUE_ELEMENT)) {
                      String v = getTextValue(e).trim();
                      if (interfaceNames.contains(v)) {
                          throw new ComponentDefinitionException("The element " + INTERFACES_ELEMENT + " should not contain the same interface twice");
                      }
                      interfaceNames.add(getTextValue(e));
                  } else {
                      throw new ComponentDefinitionException("Unsupported element " + e.getNodeName() + " inside an " + INTERFACES_ELEMENT + " element");
                  }
              }
          }
          return interfaceNames;
      }
  }
  