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.spring.extender;
  
  import java.net.URI;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Set;
  
  import org.apache.aries.blueprint.NamespaceHandler;
  import org.apache.aries.blueprint.ParserContext;
  import org.apache.aries.blueprint.mutable.MutableRefMetadata;
  import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
  import org.apache.aries.blueprint.mutable.MutableServiceMetadata;
  import org.apache.aries.blueprint.mutable.MutableValueMetadata;
  import org.osgi.service.blueprint.reflect.BeanMetadata;
  import org.osgi.service.blueprint.reflect.ComponentMetadata;
  import org.osgi.service.blueprint.reflect.Metadata;
  import org.osgi.service.blueprint.reflect.NonNullMetadata;
  import org.osgi.service.blueprint.reflect.ReferenceMetadata;
  import org.osgi.service.blueprint.reflect.ServiceMetadata;
  import org.osgi.service.blueprint.reflect.Target;
  import org.springframework.util.Assert;
  import org.springframework.util.StringUtils;
  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;
  
  public class SpringOsgiNamespaceHandler implements NamespaceHandler {
  
      public static final String BLUEPRINT_NAMESPACE = "http://www.osgi.org/xmlns/blueprint/v1.0.0";
      public static final String SPRING_NAMESPACE = "http://www.springframework.org/schema/beans";
  
      public static final String BEAN_ELEMENT = "bean";
      public static final String BEAN_NAME_ELEMENT = "bean-name";
      public static final String FILTER_ATTRIBUTE = "filter";
      public static final String INTERFACE_ATTRIBUTE = "interface";
      public static final String TIMEOUT_ATTRIBUTE = "timeout";
      public static final String DEPENDS_ON_ATTRIBUTE = "depends-on";
      public static final String CARDINALITY_ATTRIBUTE = "cardinality";
      public static final String LISTENER_ELEMENT = "listener";
      public static final String REF_ATTRIBUTE = "ref";
      public static final String BIND_METHOD_ATTRIBUTE = "bind-method";
      public static final String UNBIND_METHOD_ATTRIBUTE = "unbind-method";
      public static final String ID_ATTRIBUTE = "id";
      public static final String CARDINALITY_0_1 = "0..1";
      public static final String VALUE_ATTRIBUTE = "value";
      public static final String VALUE_REF_ATTRIBUTE = "value-ref";
      public static final String KEY_ATTRIBUTE = "key";
      public static final String KEY_REF_ATTRIBUTE = "key-ref";
      public static final String ENTRY_ELEMENT = "entry";
      public static final String SERVICE_PROPERTIES_ELEMENT = "service-properties";
      public static final String REGISTRATION_LISTENER_ELEMENT = "registration-listener";
      public static final String INTERFACES_ELEMENT = "interfaces";
      public static final String VALUE_ELEMENT = "value";
      public static final String AUTO_EXPORT_ATTRIBUTE = "auto-export";
      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_CLASSES = "all-classes";
      public static final String RANKING_ATTRIBUTE = "ranking";
      public static final String REFERENCE_ELEMENT = "reference";
      public static final String SERVICE_ELEMENT = "service";
      public static final String BUNDLE_ELEMENT = "bundle";
      public static final String SET_ELEMENT = "set";
      public static final String LIST_ELEMENT = "list";
      public static final int DEFAULT_TIMEOUT = 300000;
      public static final String REGISTRATION_METHOD_ATTRIBUTE = "registration-method";
      public static final String UNREGISTRATION_METHOD_ATTRIBUTE = "unregistration-method";
  
      private int idCounter;
  
      @Override
      public URL getSchemaLocation(String namespace) {
          if (namespace.startsWith("http://www.springframework.org/schema/osgi/spring-osgi")) {
              String sub = namespace.substring("http://www.springframework.org/schema/osgi/".length());
              if ("spring-osgi.xsd".equals(sub)) {
                  sub = "spring-osgi-1.2.xsd";
              }
              return getClass().getResource(sub);
          }
          return null;
      }
  
      @Override
      public Set<Class> getManagedClasses() {
          return null;
      }
  
      @Override
      public Metadata parse(Element element, ParserContext context) {
          if (REFERENCE_ELEMENT.equals(element.getLocalName())) {
              return parseReference(element, context);
          }
          else if (SERVICE_ELEMENT.equals(element.getLocalName())) {
              return parseService(element, context);
          }
          else if (BUNDLE_ELEMENT.equals(element.getLocalName())) {
              return parseBundle(element, context);
          }
          else if (SET_ELEMENT.equals(element.getLocalName())) {
              return parseSet(element, context);
          }
          else if (LIST_ELEMENT.equals(element.getLocalName())) {
              return parseList(element, context);
          }
          else {
              throw new UnsupportedOperationException();
          }
      }
  
      private Metadata parseBundle(Element element, ParserContext context) {
          throw new UnsupportedOperationException();
      }
  
      private Metadata parseList(Element element, ParserContext context) {
          // TODO: support list
          throw new UnsupportedOperationException();
      }
  
      private Metadata parseSet(Element element, ParserContext context) {
          // TODO: support set
          throw new UnsupportedOperationException();
      }
  
      private Metadata parseService(Element element, ParserContext context) {
          MutableServiceMetadata metadata = context.createMetadata(MutableServiceMetadata.class);
          // Parse attributes
          if (element.hasAttribute(ID_ATTRIBUTE)) {
              metadata.setId(element.getAttribute(ID_ATTRIBUTE));
          } else {
              metadata.setId(generateId(context));
          }
          if (nonEmpty(element.getAttribute(REF_ATTRIBUTE)) != null) {
              MutableRefMetadata ref = context.createMetadata(MutableRefMetadata.class);
              ref.setComponentId(element.getAttribute(REF_ATTRIBUTE));
              metadata.setServiceComponent(ref);
          }
          metadata.setRanking(nonEmpty(element.getAttribute(RANKING_ATTRIBUTE)) != null
                  ? Integer.parseInt(element.getAttribute(RANKING_ATTRIBUTE))
                  : 0);
          String itf = nonEmpty(element.getAttribute(INTERFACE_ATTRIBUTE));
          if (itf != null) {
              metadata.addInterface(itf);
          }
          String[] dependsOn = StringUtils.tokenizeToStringArray(nonEmpty(element.getAttribute(DEPENDS_ON_ATTRIBUTE)), ",; ");
          metadata.setDependsOn(dependsOn != null ? Arrays.asList(dependsOn) : null);
          String autoExp = nonEmpty(element.getAttribute(AUTO_EXPORT_ATTRIBUTE));
          if (AUTO_EXPORT_INTERFACES.equals(autoExp)) {
              metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_INTERFACES);
          } else if (AUTO_EXPORT_CLASS_HIERARCHY.equals(autoExp)) {
              metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_CLASS_HIERARCHY);
          } else if (AUTO_EXPORT_ALL_CLASSES.equals(autoExp)) {
              metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
          } else {
              metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_DISABLED);
          }
          // TODO: @context-class-loader
          // Parse child elements
          for (Element child : getChildren(element)) {
              if (element.getNamespaceURI().equals(child.getNamespaceURI())) {
                  if (INTERFACES_ELEMENT.equals(child.getLocalName())) {
                      List<String> itfs = parseInterfaces(child);
                      for (String intf : itfs) {
                          metadata.addInterface(intf);
                      }
                  }
                  else if (REGISTRATION_LISTENER_ELEMENT.equals(child.getLocalName())) {
                      String regMethod = nonEmpty(child.getAttribute(REGISTRATION_METHOD_ATTRIBUTE));
                      String unregMethod = nonEmpty(child.getAttribute(UNREGISTRATION_METHOD_ATTRIBUTE));
                      String refStr = nonEmpty(child.getAttribute(REF_ATTRIBUTE));
                      Target listenerComponent = null;
                      if (refStr != null) {
                          MutableRefMetadata ref = context.createMetadata(MutableRefMetadata.class);
                          ref.setComponentId(refStr);
                          listenerComponent = ref;
                      }
                      for (Element cchild : getChildren(child)) {
                          if (listenerComponent != null) {
                              throw new IllegalArgumentException("Only one of @ref attribute or inlined bean definition element is allowed");
                          }
                          listenerComponent = parseInlinedTarget(context, metadata, cchild);
                      }
                      if (listenerComponent == null) {
                          throw new IllegalArgumentException("Missing @ref attribute or inlined bean definition element");
                      }
                      metadata.addRegistrationListener(listenerComponent, regMethod, unregMethod);
                  }
                  else if (SERVICE_PROPERTIES_ELEMENT.equals(child.getLocalName())) {
                      // TODO: @key-type
                      for (Element e : getChildren(child)) {
                          if (ENTRY_ELEMENT.equals(e.getLocalName())) {
                              NonNullMetadata key;
                              Metadata val;
                              boolean hasKeyAttribute = e.hasAttribute(KEY_ATTRIBUTE);
                              boolean hasKeyRefAttribute = e.hasAttribute(KEY_REF_ATTRIBUTE);
                              if (hasKeyRefAttribute && !hasKeyAttribute) {
                                  MutableRefMetadata r = context.createMetadata(MutableRefMetadata.class);
                                  r.setComponentId(e.getAttribute(KEY_REF_ATTRIBUTE));
                                  key = r;
                              } else if (hasKeyAttribute && !hasKeyRefAttribute) {
                                  MutableValueMetadata v = context.createMetadata(MutableValueMetadata.class);
                                  v.setStringValue(e.getAttribute(KEY_ATTRIBUTE));
                                  key = v;
                              } else {
                                  throw new IllegalStateException("Either key or key-ref must be specified");
                              }
                              // TODO: support key
                              boolean hasValAttribute = e.hasAttribute(VALUE_ATTRIBUTE);
                              boolean hasValRefAttribute = e.hasAttribute(VALUE_REF_ATTRIBUTE);
                              if (hasValRefAttribute && !hasValAttribute) {
                                  MutableRefMetadata r = context.createMetadata(MutableRefMetadata.class);
                                  r.setComponentId(e.getAttribute(VALUE_REF_ATTRIBUTE));
                                  val = r;
                              } else if (hasValAttribute && !hasValRefAttribute) {
                                  MutableValueMetadata v = context.createMetadata(MutableValueMetadata.class);
                                  v.setStringValue(e.getAttribute(VALUE_ATTRIBUTE));
                                  val = v;
                              } else {
                                  throw new IllegalStateException("Either val or val-ref must be specified");
                              }
                              // TODO: support children elements ?
                              metadata.addServiceProperty(key, val);
                          }
                      }
                  }
              }
              else if (BLUEPRINT_NAMESPACE.equals(child.getNamespaceURI())
                      && BEAN_ELEMENT.equals(child.getLocalName())) {
                  if (metadata.getServiceComponent() != null) {
                      throw new IllegalArgumentException("Only one of @ref attribute and bean element is allowed");
                  }
                  Target bean = context.parseElement(BeanMetadata.class, metadata, child);
                  metadata.setServiceComponent(bean);
              }
              else {
                  if (metadata.getServiceComponent() != null) {
                      throw new IllegalArgumentException("Only one of @ref attribute or inlined bean definition element is allowed");
                  }
                  NamespaceHandler handler = context.getNamespaceHandler(URI.create(child.getNamespaceURI()));
                  if (handler == null) {
                      throw new IllegalStateException("No NamespaceHandler found for " + child.getNamespaceURI());
                  }
                  Metadata md = handler.parse(child, context);
                  if (!(md instanceof Target)) {
                      throw new IllegalStateException("NamespaceHandler did not return a Target instance but " + md);
                  }
                  metadata.setServiceComponent((Target) md);
              }
          }
          return metadata;
      }
  
      private Metadata parseReference(Element element, ParserContext context) {
          MutableReferenceMetadata metadata = context.createMetadata(MutableReferenceMetadata.class);
          // Parse attributes
          if (element.hasAttribute(ID_ATTRIBUTE)) {
              metadata.setId(element.getAttribute(ID_ATTRIBUTE));
          } else {
              metadata.setId(generateId(context));
          }
          metadata.setAvailability(CARDINALITY_0_1.equals(element.getAttribute(CARDINALITY_ATTRIBUTE))
                  ? ReferenceMetadata.AVAILABILITY_OPTIONAL
                  : ReferenceMetadata.AVAILABILITY_MANDATORY);
          metadata.setTimeout(getLong(element.getAttribute(TIMEOUT_ATTRIBUTE), DEFAULT_TIMEOUT));
          metadata.setInterface(element.getAttribute(INTERFACE_ATTRIBUTE));
          metadata.setFilter(element.getAttribute(FILTER_ATTRIBUTE));
          String[] dependsOn = StringUtils.tokenizeToStringArray(element.getAttribute(DEPENDS_ON_ATTRIBUTE), ",; ");
          metadata.setDependsOn(dependsOn != null ? Arrays.asList(dependsOn) : null);
          metadata.setComponentName(element.getAttribute(BEAN_NAME_ELEMENT));
          // TODO: @context-class-loader
          // Parse child elements
          for (Element child : getChildren(element)) {
              if (element.getNamespaceURI().equals(child.getNamespaceURI())) {
                  if (INTERFACES_ELEMENT.equals(child.getLocalName())) {
                      List<String> itfs = parseInterfaces(child);
                      metadata.setExtraInterfaces(itfs);
                  }
                  else if (LISTENER_ELEMENT.equals(child.getLocalName())) {
                      String bindMethod = nonEmpty(child.getAttribute(BIND_METHOD_ATTRIBUTE));
                      String unbindMethod = nonEmpty(child.getAttribute(UNBIND_METHOD_ATTRIBUTE));
                      String refStr = nonEmpty(child.getAttribute(REF_ATTRIBUTE));
                      Target listenerComponent = null;
                      if (refStr != null) {
                          MutableRefMetadata ref = context.createMetadata(MutableRefMetadata.class);
                          ref.setComponentId(refStr);
                          listenerComponent = ref;
                      }
                      for (Element cchild : getChildren(child)) {
                          if (listenerComponent != null) {
                              throw new IllegalArgumentException("Only one of @ref attribute or inlined bean definition element is allowed");
                          }
                          listenerComponent = parseInlinedTarget(context, metadata, cchild);
                      }
                      if (listenerComponent == null) {
                          throw new IllegalArgumentException("Missing @ref attribute or inlined bean definition element");
                      }
                      metadata.addServiceListener(listenerComponent, bindMethod, unbindMethod);
                  }
              }
              else {
                  throw new UnsupportedOperationException("Custom namespaces not supported");
              }
          }
          return metadata;
      }
  
      private Target parseInlinedTarget(ParserContext context, ComponentMetadata metadata, Element element) {
          Target listenerComponent;
          if (BLUEPRINT_NAMESPACE.equals(element.getNamespaceURI())
                  && BEAN_ELEMENT.equals(element.getLocalName())) {
              listenerComponent = context.parseElement(BeanMetadata.class, metadata, element);
          }
          else {
              NamespaceHandler handler = context.getNamespaceHandler(URI.create(element.getNamespaceURI()));
              if (handler == null) {
                  throw new IllegalStateException("No NamespaceHandler found for " + element.getNamespaceURI());
              }
              Metadata md = handler.parse(element, context);
              if (!(md instanceof Target)) {
                  throw new IllegalStateException("NamespaceHandler did not return a Target instance but " + md);
              }
              listenerComponent = (Target) md;
          }
          return listenerComponent;
      }
  
      private List<String> parseInterfaces(Element element) {
          List<String> extra = new ArrayList<String>();
          for (Element e : getChildren(element)) {
              if (VALUE_ELEMENT.equals(e.getLocalName())) {
                  extra.add(getTextValue(e));
              } else {
                  // The schema support all kind of children for a list type
                  // The type for the spring property is converted to a Class[] array
                  // TODO: support other elements ?
                  throw new UnsupportedOperationException("Unsupported child: " + element.getLocalName());
              }
          }
          return extra;
      }
  
      private String nonEmpty(String ref) {
          return ref != null && ref.isEmpty() ? null : ref;
      }
  
      private String generateId(ParserContext context) {
          String id;
          do {
              id = ".spring-osgi-" + ++idCounter;
          } while (context.getComponentDefinitionRegistry().containsComponentDefinition(id));
          return id;
      }
  
      @Override
      public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
          return component;
      }
  
      private List<Element> getChildren(Element element) {
          List<Element> children = new ArrayList<Element>();
          for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
              if (child instanceof Element) {
                  children.add((Element) child);
              }
          }
          return children;
      }
  
      private long getLong(String str, long def) {
          if (str == null || str.isEmpty()) {
              return def;
          } else {
              return Long.parseLong(str);
          }
      }
  
      public static String getTextValue(Element valueEle) {
          Assert.notNull(valueEle, "Element must not be null");
          StringBuilder sb = new StringBuilder();
          NodeList nl = valueEle.getChildNodes();
          for(int i = 0, l = nl.getLength(); i < l; ++i) {
              Node item = nl.item(i);
              if(item instanceof CharacterData && !(item instanceof Comment) || item instanceof EntityReference) {
                  sb.append(item.getNodeValue());
              }
          }
          return sb.toString();
      }
  }
  