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;
  
  import java.io.IOException;
  import java.net.URI;
  import java.net.URL;
  import java.util.Collections;
  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.NamespaceHandler2;
  import org.apache.aries.blueprint.ParserContext;
  import org.apache.aries.blueprint.PassThroughMetadata;
  import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
  import org.apache.aries.blueprint.mutable.MutablePassThroughMetadata;
  import org.apache.aries.blueprint.mutable.MutableRefMetadata;
  import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
  import org.osgi.framework.Bundle;
  import org.osgi.service.blueprint.reflect.BeanMetadata;
  import org.osgi.service.blueprint.reflect.ComponentMetadata;
  import org.osgi.service.blueprint.reflect.Metadata;
  import org.springframework.beans.factory.BeanFactory;
  import org.springframework.beans.factory.config.BeanDefinition;
  import org.springframework.beans.factory.parsing.EmptyReaderEventListener;
  import org.springframework.beans.factory.parsing.FailFastProblemReporter;
  import org.springframework.beans.factory.parsing.NullSourceExtractor;
  import org.springframework.beans.factory.parsing.ProblemReporter;
  import org.springframework.beans.factory.parsing.ReaderEventListener;
  import org.springframework.beans.factory.parsing.SourceExtractor;
  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  import org.springframework.beans.factory.support.DefaultListableBeanFactory;
  import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
  import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
  import org.springframework.beans.factory.xml.XmlReaderContext;
  import org.springframework.context.ApplicationContext;
  import org.springframework.core.io.Resource;
  import org.springframework.core.io.UrlResource;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.xml.sax.EntityResolver;
  import org.xml.sax.InputSource;
  
  /**
   * Blueprint NamespaceHandler wrapper for a spring NamespaceHandler
   */
  public class BlueprintNamespaceHandler implements NamespaceHandler, NamespaceHandler2 {
  
      public static final String SPRING_CONTEXT_ID = "." + org.springframework.beans.factory.xml.ParserContext.class.getName();
      public static final String SPRING_BEAN_PROCESSOR_ID = "." + SpringBeanProcessor.class.getName();
      public static final String SPRING_APPLICATION_CONTEXT_ID = "." + ApplicationContext.class.getName();
      public static final String SPRING_BEAN_FACTORY_ID = "." + BeanFactory.class.getName();
  
      private final Bundle bundle;
      private final Properties schemas;
      private final org.springframework.beans.factory.xml.NamespaceHandler springHandler;
  
      public BlueprintNamespaceHandler(Bundle bundle, Properties schemas, org.springframework.beans.factory.xml.NamespaceHandler springHandler) {
          this.bundle = bundle;
          this.schemas = schemas;
          this.springHandler = springHandler;
          springHandler.init();
      }
  
      public org.springframework.beans.factory.xml.NamespaceHandler getSpringHandler() {
          return springHandler;
      }
  
      @Override
      public boolean usePsvi() {
          return true;
      }
  
      @Override
      public URL getSchemaLocation(String s) {
          if (schemas.containsKey(s)) {
              return bundle.getResource(schemas.getProperty(s));
          }
          return null;
      }
  
      @Override
      public Set<Class> getManagedClasses() {
          return Collections.<Class>singleton(BeanDefinition.class);
      }
  
      @Override
      public Metadata parse(Element element, ParserContext parserContext) {
          try {
              // Get the spring context
              org.springframework.beans.factory.xml.ParserContext springContext
                      = getOrCreateParserContext(parserContext);
              // Parse spring bean
              BeanDefinition bd = springHandler.parse(element, springContext);
              for (String name : springContext.getRegistry().getBeanDefinitionNames()) {
                  if (springContext.getRegistry().getBeanDefinition(name) == bd) {
                      ComponentDefinitionRegistry registry = parserContext.getComponentDefinitionRegistry();
                      if (registry.containsComponentDefinition(name)) {
                          // Hack: we can't really make the difference between a top level bean
                          // and an inlined bean when using custom (eventually nested) namespaces.
                          // To work around the problem, the BlueprintBeanFactory will always register
                          // a BeanMetadata for each bean, but here, we unregister it and return it instead
                          // so that the caller is responsible for registering the metadata.
                          ComponentMetadata metadata = registry.getComponentDefinition(name);
                          registry.removeComponentDefinition(name);
                          return metadata;
                      }
                  }
              }
              return null;
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public ComponentMetadata decorate(Node node, ComponentMetadata componentMetadata, ParserContext parserContext) {
          return componentMetadata;
      }
  
      private org.springframework.beans.factory.xml.ParserContext getOrCreateParserContext(ParserContext parserContext) {
          ComponentDefinitionRegistry registry = parserContext.getComponentDefinitionRegistry();
          ExtendedBlueprintContainer container = getBlueprintContainer(parserContext);
          // Create spring application context
          SpringApplicationContext applicationContext = getPassThrough(parserContext,
                  SPRING_APPLICATION_CONTEXT_ID, SpringApplicationContext.class);
          if (applicationContext == null) {
              applicationContext = new SpringApplicationContext(container);
              registry.registerComponentDefinition(createPassThrough(parserContext,
                      SPRING_APPLICATION_CONTEXT_ID, applicationContext, "close"
              ));
          }
          // Create registry
          DefaultListableBeanFactory beanFactory = getPassThrough(parserContext,
                  SPRING_BEAN_FACTORY_ID, DefaultListableBeanFactory.class);
          if (beanFactory == null) {
              beanFactory = applicationContext.getBeanFactory();
              registry.registerComponentDefinition(createPassThrough(parserContext,
                      SPRING_BEAN_FACTORY_ID, beanFactory
              ));
          }
          // Create spring parser context
          org.springframework.beans.factory.xml.ParserContext springParserContext
                  = getPassThrough(parserContext, SPRING_CONTEXT_ID, org.springframework.beans.factory.xml.ParserContext.class);
          if (springParserContext == null) {
              // Create spring context
              springParserContext = createSpringParserContext(parserContext, beanFactory);
              registry.registerComponentDefinition(createPassThrough(parserContext,
                      SPRING_CONTEXT_ID, springParserContext
              ));
          }
          // Create processor
          if (!parserContext.getComponentDefinitionRegistry().containsComponentDefinition(SPRING_BEAN_PROCESSOR_ID)) {
              MutableBeanMetadata bm = parserContext.createMetadata(MutableBeanMetadata.class);
              bm.setId(SPRING_BEAN_PROCESSOR_ID);
              bm.setProcessor(true);
              bm.setScope(BeanMetadata.SCOPE_SINGLETON);
              bm.setRuntimeClass(SpringBeanProcessor.class);
              bm.setActivation(BeanMetadata.ACTIVATION_EAGER);
              bm.addArgument(createRef(parserContext, "blueprintBundleContext"), null, 0);
              bm.addArgument(createRef(parserContext, "blueprintContainer"), null, 0);
              bm.addArgument(createRef(parserContext, SPRING_APPLICATION_CONTEXT_ID), null, 0);
              registry.registerComponentDefinition(bm);
          }
          // Add the namespace handlers' bundle to the application context classloader
          for (URI uri : parserContext.getNamespaces()) {
              NamespaceHandler ns = parserContext.getNamespaceHandler(uri);
              if (ns instanceof BlueprintNamespaceHandler) {
                  applicationContext.addSourceBundle(((BlueprintNamespaceHandler) ns).bundle);
              }
          }
          return springParserContext;
      }
  
      private ComponentMetadata createPassThrough(ParserContext parserContext, String id, Object o) {
          MutablePassThroughMetadata pt = parserContext.createMetadata(MutablePassThroughMetadata.class);
          pt.setId(id);
          pt.setObject(o);
          return pt;
      }
  
      private ComponentMetadata createPassThrough(ParserContext parserContext, String id, Object o, String destroy) {
          MutablePassThroughMetadata pt = parserContext.createMetadata(MutablePassThroughMetadata.class);
          pt.setId(id + ".factory");
          pt.setObject(new Holder(o));
          MutableBeanMetadata b = parserContext.createMetadata(MutableBeanMetadata.class);
          b.setId(id);
          b.setFactoryComponent(pt);
          b.setFactoryMethod("getObject");
          b.setDestroyMethod(destroy);
          return b;
      }
  
      public static class Holder {
          private final Object object;
  
          public Holder(Object object) {
              this.object = object;
          }
  
          public Object getObject() {
              return object;
          }
      }
  
      private Metadata createRef(ParserContext parserContext, String id) {
          MutableRefMetadata ref = parserContext.createMetadata(MutableRefMetadata.class);
          ref.setComponentId(id);
          return ref;
      }
  
      private ExtendedBlueprintContainer getBlueprintContainer(ParserContext parserContext) {
          ExtendedBlueprintContainer container = getPassThrough(parserContext, "blueprintContainer", ExtendedBlueprintContainer.class);
          if (container == null) {
              throw new IllegalStateException();
          }
          return container;
      }
  
      @SuppressWarnings("unchecked")
      private <T> T getPassThrough(ParserContext parserContext, String name, Class<T> clazz) {
          Metadata metadata = parserContext.getComponentDefinitionRegistry().getComponentDefinition(name);
          if (metadata instanceof BeanMetadata) {
              BeanMetadata bm = (BeanMetadata) metadata;
              if (bm.getFactoryComponent() instanceof PassThroughMetadata
                      && "getObject".equals(bm.getFactoryMethod())) {
                  metadata = bm.getFactoryComponent();
              }
          }
          if (metadata instanceof PassThroughMetadata) {
              Object o = ((PassThroughMetadata) metadata).getObject();
              if (o instanceof Holder) {
                  o = ((Holder) o).getObject();
              }
              return (T) o;
          } else {
              return null;
          }
      }
  
      private org.springframework.beans.factory.xml.ParserContext createSpringParserContext(final ParserContext parserContext, DefaultListableBeanFactory registry) {
          try {
              SpringVersionBridgeXmlBeanDefinitionReader xbdr = new SpringVersionBridgeXmlBeanDefinitionReader(registry);
              Resource resource = new UrlResource(parserContext.getSourceNode().getOwnerDocument().getDocumentURI());
              ProblemReporter problemReporter = new FailFastProblemReporter();
              ReaderEventListener listener = new EmptyReaderEventListener();
              SourceExtractor extractor = new NullSourceExtractor();
              NamespaceHandlerResolver resolver = new SpringNamespaceHandlerResolver(parserContext);
              xbdr.setProblemReporter(problemReporter);
              xbdr.setEventListener(listener);
              xbdr.setSourceExtractor(extractor);
              xbdr.setNamespaceHandlerResolver(resolver);
              xbdr.setEntityResolver(new EntityResolver() {
                  @Override
                  public InputSource resolveEntity(String publicId, String systemId) throws IOException {
                      for (URI ns : parserContext.getNamespaces()) {
                          NamespaceHandler nh = parserContext.getNamespaceHandler(ns);
                          URL url = null;
                          if (publicId != null) {
                              url = nh.getSchemaLocation(publicId);
                          }
                          if (url == null && systemId != null) {
                              url = nh.getSchemaLocation(systemId);
                          }
                          if (url != null) {
                              InputSource is = new InputSource();
                              is.setPublicId(publicId);
                              is.setSystemId(systemId);
                              is.setByteStream(url.openStream());
                              return is;
                          }
                      }
                      return null;
                  }
              });
              XmlReaderContext xmlReaderContext = xbdr.createReaderContext(resource);
              BeanDefinitionParserDelegate bdpd = new BeanDefinitionParserDelegate(xmlReaderContext);
              return new org.springframework.beans.factory.xml.ParserContext(xmlReaderContext, bdpd);
          } catch (Exception e) {
              throw new RuntimeException("Error creating spring parser context", e);
          }
      }
  
      /**
       * Some methods are protected in Spring 3.x, hence overridden methods in this class to make them available.
       */
      private class SpringVersionBridgeXmlBeanDefinitionReader extends XmlBeanDefinitionReader {
  
          public SpringVersionBridgeXmlBeanDefinitionReader(final BeanDefinitionRegistry registry) {
              super(registry);
          }
  
          @Override
          public XmlReaderContext createReaderContext(final Resource resource) {
              return super.createReaderContext(resource);
          }
      }
  
  
  }
  