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.namespace;
  
  import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
  import static javax.xml.XMLConstants.XML_NS_URI;
  import static org.apache.aries.blueprint.parser.Parser.BLUEPRINT_NAMESPACE;
  
  import java.io.Closeable;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.lang.ref.Reference;
  import java.lang.ref.SoftReference;
  import java.net.MalformedURLException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.util.AbstractMap;
  import java.util.AbstractSet;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentMap;
  import java.util.concurrent.CopyOnWriteArrayList;
  import java.util.concurrent.CopyOnWriteArraySet;
  import javax.xml.stream.XMLInputFactory;
  import javax.xml.stream.XMLStreamReader;
  import javax.xml.transform.Source;
  import javax.xml.transform.stream.StreamSource;
  import javax.xml.validation.Schema;
  import javax.xml.validation.SchemaFactory;
  
  import org.apache.aries.blueprint.NamespaceHandler;
  import org.apache.aries.blueprint.ParserContext;
  import org.apache.aries.blueprint.container.NamespaceHandlerRegistry;
  import org.apache.aries.blueprint.parser.NamespaceHandlerSet;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.BundleContext;
  import org.osgi.framework.ServiceReference;
  import org.osgi.service.blueprint.reflect.ComponentMetadata;
  import org.osgi.service.blueprint.reflect.Metadata;
  import org.osgi.util.tracker.ServiceTracker;
  import org.osgi.util.tracker.ServiceTrackerCustomizer;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.ls.LSInput;
  import org.w3c.dom.ls.LSResourceResolver;
  import org.xml.sax.SAXException;
  
  /**
   * Default implementation of the NamespaceHandlerRegistry.
   * 
   * This registry will track NamespaceHandler objects in the OSGi registry and make
   * them available, calling listeners when handlers are registered or unregistered.
   *
   * @version $Rev$, $Date$
   */
  public class NamespaceHandlerRegistryImpl implements NamespaceHandlerRegistry, ServiceTrackerCustomizer {
      
      public static final String NAMESPACE = "osgi.service.blueprint.namespace";
  
      private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceHandlerRegistryImpl.class);
  
      // The bundle context is thread safe
      private final BundleContext bundleContext;
  
      // The service tracker is thread safe
      private final ServiceTracker tracker;
  
      // The handlers map is concurrent
      private final ConcurrentHashMap<URI, CopyOnWriteArraySet<NamespaceHandler>> handlers =
                          new ConcurrentHashMap<URI, CopyOnWriteArraySet<NamespaceHandler>>();
  
      // Access to the LRU schemas map is synchronized on itself
      private final LRUMap<Map<URI, NamespaceHandler>, Reference<Schema>> schemas =
                          new LRUMap<Map<URI, NamespaceHandler>, Reference<Schema>>(10);
  
      // Access to this factory is synchronized on itself
      private final SchemaFactory schemaFactory =
                          SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
  
      // Access to this variable is must be synchronized on itself
      private final ArrayList<NamespaceHandlerSetImpl> sets =
                          new ArrayList<NamespaceHandlerSetImpl>();
  
      public NamespaceHandlerRegistryImpl(BundleContext bundleContext) {
          this.bundleContext = bundleContext;
          tracker = new ServiceTracker(bundleContext, NamespaceHandler.class.getName(), this);
          tracker.open();
      }
  
      public Object addingService(ServiceReference reference) {
          LOGGER.debug("Adding NamespaceHandler " + reference.toString());
          NamespaceHandler handler = (NamespaceHandler) bundleContext.getService(reference);
          if (handler != null) {
              try {
                  Map<String, Object> props = new HashMap<String, Object>();
                  for (String name : reference.getPropertyKeys()) {
                      props.put(name, reference.getProperty(name));
                  }
                  registerHandler(handler, props);
              } catch (Exception e) {
                  LOGGER.warn("Error registering NamespaceHandler", e);
              }
          } else {
              Bundle bundle = reference.getBundle();
              // If bundle is null, the service has already been unregistered,
              // so do nothing in that case
              if (bundle != null) {
                  LOGGER.warn("Error resolving NamespaceHandler, null Service obtained from tracked ServiceReference {} for bundle {}/{}",
                          reference.toString(), reference.getBundle().getSymbolicName(), reference.getBundle().getVersion());
              }
          }
          return handler;
      }
  
      public void modifiedService(ServiceReference reference, Object service) {
          removedService(reference, service);
          addingService(reference);
      }
  
      public void removedService(ServiceReference reference, Object service) {
          try {
              LOGGER.debug("Removing NamespaceHandler " + reference.toString());
              NamespaceHandler handler = (NamespaceHandler) service;
              Map<String, Object> props = new HashMap<String, Object>();
              for (String name : reference.getPropertyKeys()) {
                  props.put(name, reference.getProperty(name));
              }
              unregisterHandler(handler, props);
          } catch (Exception e) {
              LOGGER.warn("Error unregistering NamespaceHandler", e);
          }
      }
  
      public void registerHandler(NamespaceHandler handler, Map properties) {
          List<URI> namespaces = getNamespaces(properties);
          for (URI uri : namespaces) {
              CopyOnWriteArraySet<NamespaceHandler> h = handlers.putIfAbsent(uri, new CopyOnWriteArraySet<NamespaceHandler>());
              if (h == null) {
                  h = handlers.get(uri);
              }
              if (h.add(handler)) {
                  List<NamespaceHandlerSetImpl> sets;
                  synchronized (this.sets) {
                      sets = new ArrayList<NamespaceHandlerSetImpl>(this.sets);
                  }
                  for (NamespaceHandlerSetImpl s : sets) {
                      s.registerHandler(uri, handler);
                  }
              }
          }
      }
  
      public void unregisterHandler(NamespaceHandler handler, Map properties) {
          List<URI> namespaces = getNamespaces(properties);
          for (URI uri : namespaces) {
              CopyOnWriteArraySet<NamespaceHandler> h = handlers.get(uri);
              if (!h.remove(handler)) {
                  continue;
              }
              List<NamespaceHandlerSetImpl> sets;
              synchronized (this.sets) {
                  sets = new ArrayList<NamespaceHandlerSetImpl>(this.sets);
              }
              for (NamespaceHandlerSetImpl s : sets) {
                  s.unregisterHandler(uri, handler);
              }
          }
          removeSchemasFor(handler);
      }
  
      private static List<URI> getNamespaces(Map properties) {
          Object ns = properties != null ? properties.get(NAMESPACE) : null;
          if (ns == null) {
              throw new IllegalArgumentException("NamespaceHandler service does not have an associated "
                              + NAMESPACE + " property defined");
          } else if (ns instanceof URI[]) {
              return Arrays.asList((URI[]) ns);
          } else if (ns instanceof URI) {
              return Collections.singletonList((URI) ns);
          } else if (ns instanceof String) {
              return Collections.singletonList(URI.create((String) ns));
          } else if (ns instanceof String[]) {
              String[] strings = (String[]) ns;
              List<URI> namespaces = new ArrayList<URI>(strings.length);
              for (String string : strings) {
                  namespaces.add(URI.create(string));
              }
              return namespaces;
          } else if (ns instanceof Collection) {
              Collection col = (Collection) ns;
              List<URI> namespaces = new ArrayList<URI>(col.size());
              for (Object o : col) {
                  namespaces.add(toURI(o));
              }
              return namespaces;
          } else if (ns instanceof Object[]) {
              Object[] array = (Object[]) ns;
              List<URI> namespaces = new ArrayList<URI>(array.length);
              for (Object o : array) {
                  namespaces.add(toURI(o));
              }
              return namespaces;
          } else {
              throw new IllegalArgumentException("NamespaceHandler service has an associated "
                              + NAMESPACE + " property defined which can not be converted to an array of URI");
          }
      }
  
      private static URI toURI(Object o) {
          if (o instanceof URI) {
              return (URI) o;
          } else if (o instanceof String) {
              return URI.create((String) o);
          } else {
              throw new IllegalArgumentException("NamespaceHandler service has an associated "
                              + NAMESPACE + " property defined which can not be converted to an array of URI");
          }
      }
      
      public NamespaceHandlerSet getNamespaceHandlers(Set<URI> uris, Bundle bundle) {
          NamespaceHandlerSetImpl s;
          synchronized (sets) {
              s = new NamespaceHandlerSetImpl(uris, bundle);
              sets.add(s);
          }
          return s;
      }
  
      public void destroy() {
          tracker.close();
      }
  
      private Schema getExistingSchema(Map<URI, NamespaceHandler> handlers) {
          synchronized (schemas) {
              for (Map<URI, NamespaceHandler> key : schemas.keySet()) {
                  boolean found = true;
                  for (URI uri : handlers.keySet()) {
                      if (!handlers.get(uri).equals(key.get(uri))) {
                          found = false;
                          break;
                      }
                  }
                  if (found) {
                      return schemas.get(key).get();
                  }
              }
              return null;
          }
      }
  
      private void removeSchemasFor(NamespaceHandler handler) {
          synchronized (schemas) {
              List<Map<URI, NamespaceHandler>> keys = new ArrayList<Map<URI, NamespaceHandler>>();
              for (Map<URI, NamespaceHandler> key : schemas.keySet()) {
                  if (key.values().contains(handler)) {
                      keys.add(key);
                  }
              }
              for (Map<URI, NamespaceHandler> key : keys) {
                  schemas.remove(key);
              }
          }
      }
  
      private void cacheSchema(Map<URI, NamespaceHandler> handlers, Schema schema) {
          synchronized (schemas) {
              // Remove schemas that are fully included
              for (Iterator<Map<URI, NamespaceHandler>> iterator = schemas.keySet().iterator(); iterator.hasNext();) {
                  Map<URI, NamespaceHandler> key = iterator.next();
                  boolean found = true;
                  for (URI uri : key.keySet()) {
                      if (!key.get(uri).equals(handlers.get(uri))) {
                          found = false;
                          break;
                      }
                  }
                  if (found) {
                      iterator.remove();
                      break;
                  }
              }
              // Add our new schema
              schemas.put(handlers, new SoftReference<Schema>(schema));
          }
      }
  
      private static void closeQuietly(Closeable closeable) {
          try {
              if (closeable != null) {
                  closeable.close();
              }
          } catch (IOException e) {
              // Ignore
          }
      }
  
      private static class SourceLSInput implements LSInput {
          private final StreamSource source;
  
          public SourceLSInput(StreamSource source) {
              this.source = source;
          }
  
          public Reader getCharacterStream() {
              return null;
          }
  
          public void setCharacterStream(Reader characterStream) {
          }
  
          public InputStream getByteStream() {
              return source.getInputStream();
          }
  
          public void setByteStream(InputStream byteStream) {
          }
  
          public String getStringData() {
              return null;
          }
  
          public void setStringData(String stringData) {
          }
  
          public String getSystemId() {
              return source.getSystemId();
          }
  
          public void setSystemId(String systemId) {
          }
  
          public String getPublicId() {
              return null;
          }
  
          public void setPublicId(String publicId) {
          }
  
          public String getBaseURI() {
              return null;
          }
  
          public void setBaseURI(String baseURI) {
          }
  
          public String getEncoding() {
              return null;
          }
  
          public void setEncoding(String encoding) {
          }
  
          public boolean getCertifiedText() {
              return false;
          }
  
          public void setCertifiedText(boolean certifiedText) {
          }
      }
  
      protected class NamespaceHandlerSetImpl implements NamespaceHandlerSet {
  
          private final List<Listener> listeners;
          private final Bundle bundle;
          private final Set<URI> namespaces;
          private final Map<URI, NamespaceHandler> handlers;
          private final Properties schemaMap = new Properties();
          private Schema schema;
  
          public NamespaceHandlerSetImpl(Set<URI> namespaces, Bundle bundle) {
              this.listeners = new CopyOnWriteArrayList<Listener>();
              this.namespaces = new HashSet<URI>(namespaces);
              this.bundle = bundle;
              handlers = new ConcurrentHashMap<URI, NamespaceHandler>();
              for (URI ns : namespaces) {
                  findCompatibleNamespaceHandler(ns);
              }
              URL url = bundle.getResource("OSGI-INF/blueprint/schema.map");
              if (url != null) {
                  InputStream ins = null;
                  try {
                      ins = url.openStream();
                      schemaMap.load(ins);
                  } catch (IOException ex) {
                      ex.printStackTrace();
                      //ignore
                  } finally {
                      closeQuietly(ins);
                  }
              }
              for (Object ns : schemaMap.keySet()) {
                  try {
                      this.namespaces.remove(new URI(ns.toString()));
                  } catch (URISyntaxException e) {
                      //ignore
                  }
              }
          }
  
          public boolean isComplete() {
              return handlers.size() == namespaces.size();
          }
  
          public Set<URI> getNamespaces() {
              return namespaces;
          }
  
          public NamespaceHandler getNamespaceHandler(URI namespace) {
              return handlers.get(namespace);
          }
  
          public Schema getSchema() throws SAXException, IOException {
              return getSchema(null);
          }
  
          public Schema getSchema(Map<String, String> locations) throws SAXException, IOException {
              if (!isComplete()) {
                  throw new IllegalStateException("NamespaceHandlerSet is not complete");
              }
              if (schema == null) {
                  schema = doGetSchema(locations);
              }
              return schema;
          }
  
          private Schema doGetSchema(Map<String, String> locations) throws IOException, SAXException {
              if (schemaMap != null && !schemaMap.isEmpty()) {
                  return createSchema(locations);
              }
              // Find a schema that can handle all the requested namespaces
              // If it contains additional namespaces, it should not be a problem since
              // they won't be used at all
              Schema schema = getExistingSchema(handlers);
              if (schema == null) {
                  // Create schema
                  schema = createSchema(locations);
                  cacheSchema(handlers, schema);
              }
              return schema;
          }
  
          private class Loader implements LSResourceResolver, Closeable {
              final List<StreamSource> sources = new ArrayList<StreamSource>();
              final Map<String, URL> loaded = new HashMap<String, URL>();
              final Map<String, String> namespaces = new HashMap<String, String>();
              @Override
              public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
                  // Compute id
                  String id;
                  String prevNamespace = baseURI != null ? namespaces.get(baseURI) : null;
                  if (namespaceURI != null && prevNamespace != null && namespaceURI.equals(prevNamespace)) {
                      // This is an include
                      id = getId(type, namespaceURI, publicId, systemId);
                  } else {
                      id = getId(type, namespaceURI, publicId, null);
                  }
                  // Check if it has already been loaded
                  if (loaded.containsKey(id)) {
                      return createLSInput(loaded.get(id), id, namespaceURI);
                  }
                  // Schema map
                  //-----------
                  // Use provided schema map to find the resource.
                  // If the schema map contains the namespaceURI, publicId or systemId,
                  // load the corresponding resource directly from the bundle.
                  String loc = null;
                  if (namespaceURI != null) {
                      loc = schemaMap.getProperty(namespaceURI);
                  }
                  if (loc == null && publicId != null) {
                      loc = schemaMap.getProperty(publicId);
                  }
                  if (loc == null && systemId != null) {
                      loc = schemaMap.getProperty(systemId);
                  }
                  if (loc != null) {
                      URL url = bundle.getResource(loc);
                      if (url != null) {
                          return createLSInput(url, id, namespaceURI);
                      }
                  }
                  // Relative uris
                  //---------------
                  // For relative uris, don't use the namespace handlers, but simply resolve the uri
                  // and use that one directly to load the resource.
                  String resolved = resolveIfRelative(systemId, baseURI);
                  if (resolved != null) {
                      URL url;
                      try {
                          url = new URL(resolved);
                      } catch (IOException e) {
                          throw new RuntimeException(e);
                      }
                      return createLSInput(url, id, namespaceURI);
                  }
                  // Only support xml schemas from now on
                  if (namespaceURI == null || !W3C_XML_SCHEMA_NS_URI.equals(type)) {
                      return null;
                  }
                  // We are now loading a schema, or schema part with
                  //  * notNull(namespaceURI)
                  //  * null(systemId) or absolute(systemId)
                  URI nsUri = URI.create(namespaceURI);
                  String rid = systemId != null ? systemId : namespaceURI;
                  NamespaceHandler h = getNamespaceHandler(nsUri);
                  // This is a resource from a known namespace
                  if (h != null) {
                      URL url = h.getSchemaLocation(rid);
                      if (isCorrectUrl(url)) {
                          return createLSInput(url, id, namespaceURI);
                      }
                  }
                  else {
                      // Ask known handlers if they have this schema
                      for (NamespaceHandler hd : handlers.values()) {
                          URL url = hd.getSchemaLocation(rid);
                          if (isCorrectUrl(url)) {
                              return createLSInput(url, id, namespaceURI);
                          }
                      }
                      // Find a compatible namespace handler
                      h = findCompatibleNamespaceHandler(nsUri);
                      if (h != null) {
                          URL url = h.getSchemaLocation(namespaceURI);
                          if (url == null) {
                              url = h.getSchemaLocation(rid);
                          }
                          if (isCorrectUrl(url)) {
                              LOGGER.warn("Dynamically adding namespace handler {} to {}/{}", nsUri, bundle.getSymbolicName(), bundle.getVersion());
                              return createLSInput(url, id, namespaceURI);
                          }
                      }
                  }
                  LOGGER.warn("Unable to find namespace handler for {}", namespaceURI);
                  return null;
              }
  
              public String getId(String type, String namespaceURI, String publicId, String systemId) {
                  return type + "|" + namespaceURI + "|" + publicId + "|" + systemId;
              }
  
              public StreamSource use(URL resource, String id, String namespace) throws IOException {
                  String url = resource.toExternalForm();
                  StreamSource ss = new StreamSource(resource.openStream(), url);
                  sources.add(ss);
                  loaded.put(id, resource);
                  namespaces.put(url, namespace);
                  return ss;
              }
  
              @Override
              public void close() {
                  for (StreamSource source : sources) {
                      closeQuietly(source.getInputStream());
                  }
              }
  
              public Source[] getSources() {
                  return sources.toArray(new Source[sources.size()]);
              }
  
              private boolean isCorrectUrl(URL url) {
                  return url != null && !loaded.values().contains(url);
              }
              private String resolveIfRelative(String systemId, String baseURI) {
                  if (baseURI != null && systemId != null) {
                      URI sId = URI.create(systemId);
                      if (!sId.isAbsolute()) {
                          URI resolved = URI.create(baseURI).resolve(sId);
                          if (resolved.isAbsolute()) {
                              return resolved.toString();
                          } else {
                              try {
                                  return new URL(new URL(baseURI), systemId).toString();
                              } catch (MalformedURLException e) {
                                  LOGGER.warn("Can't resolve " + systemId + " against " + baseURI);
                                  return null;
                              }
                          }
                      }
                  }
                  return null;
              }
              private LSInput createLSInput(URL url, String id, String namespace) {
                  try {
                      return new SourceLSInput(use(url, id, namespace));
                  } catch (IOException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
  
          private Schema createSchema(Map<String, String> locations) throws IOException, SAXException {
              Loader loader = new Loader();
              try {
                  loader.use(getClass().getResource("/org/osgi/service/blueprint/blueprint.xsd"),
                             loader.getId(W3C_XML_SCHEMA_NS_URI, BLUEPRINT_NAMESPACE, null, null),
                             BLUEPRINT_NAMESPACE);
                  loader.use(getClass().getResource("/org/apache/aries/blueprint/ext/impl/xml.xsd"),
                             loader.getId(W3C_XML_SCHEMA_NS_URI, XML_NS_URI, null, null),
                             XML_NS_URI);
  
                  // Create a schema for the namespaces
                  for (URI ns : handlers.keySet()) {
                      URL url = handlers.get(ns).getSchemaLocation(ns.toString());
                      if (url == null && locations != null) {
                          String loc = locations.get(ns.toString());
                          if (loc != null) {
                              url = handlers.get(ns).getSchemaLocation(loc);
                          }
                      }
                      if (url == null) {
                          LOGGER.warn("No URL is defined for schema " + ns + ". This schema will not be validated");
                      } else {
                          loader.use(url, loader.getId(W3C_XML_SCHEMA_NS_URI, ns.toString(), null, null), ns.toString());
                      }
                  }
                  for (Object ns : schemaMap.values()) {
                      URL url = bundle.getResource(ns.toString());
                      if (url == null) {
                          LOGGER.warn("No URL is defined for schema " + ns + ". This schema will not be validated");
                      } else {
                          loader.use(url, loader.getId(W3C_XML_SCHEMA_NS_URI, ns.toString(), null, null), ns.toString());
                      }
                  }
                  synchronized (schemaFactory) {
                      schemaFactory.setResourceResolver(loader);
                      return schemaFactory.newSchema(loader.getSources());
                  }
              } finally {
                  loader.close();
              }
          }
  
          public void addListener(Listener listener) {
              listeners.add(listener);
          }
  
          public void removeListener(Listener listener) {
              listeners.remove(listener);
          }
  
          public void destroy() {
              synchronized (NamespaceHandlerRegistryImpl.this.sets) {
                  NamespaceHandlerRegistryImpl.this.sets.remove(this);
              }
          }
  
          public void registerHandler(URI uri, NamespaceHandler handler) {
              if (namespaces.contains(uri) && handlers.get(uri) == null) {
                  if (findCompatibleNamespaceHandler(uri) != null) {
                      for (Listener listener : listeners) {
                          try {
                              listener.namespaceHandlerRegistered(uri);
                          } catch (Throwable t) {
                              LOGGER.debug("Unexpected exception when notifying a NamespaceHandler listener", t);
                          }
                      }
                  }
              }
          }
  
          public void unregisterHandler(URI uri, NamespaceHandler handler) {
              if (handlers.get(uri) == handler) {
                  handlers.remove(uri);
                  for (Listener listener : listeners) {
                      try {
                          listener.namespaceHandlerUnregistered(uri);
                      } catch (Throwable t) {
                          LOGGER.debug("Unexpected exception when notifying a NamespaceHandler listener", t);
                      }
                  }
              }
          }
  
          private NamespaceHandler findCompatibleNamespaceHandler(URI ns) {
              Set<NamespaceHandler> candidates = NamespaceHandlerRegistryImpl.this.handlers.get(ns);
              if (candidates != null) {
                  for (NamespaceHandler h : candidates) {
                      Set<Class> classes = h.getManagedClasses();
                      boolean compat = true;
                      if (classes != null) {
                          Set<Class> allClasses = new HashSet<Class>();
                          for (Class cl : classes) {
                              for (Class c = cl; c != null; c = c.getSuperclass()) {
                                  allClasses.add(c);
                                  for (Class i : c.getInterfaces()) {
                                      allClasses.add(i);
                                  }
                              }
                          }
                          for (Class cl : allClasses) {
                              Class clb;
                              try {
                                  clb = bundle.loadClass(cl.getName());
                                  if (clb != cl) {
                                      compat = false;
                                      break;
                                  }
                              } catch (ClassNotFoundException e) {
                                  // Ignore
                              } catch (NoClassDefFoundError e) {
                                  // Ignore
                              }
                          }
                      }
                      if (compat) {
                          namespaces.add(ns);
                          handlers.put(ns, wrapIfNeeded(h));
                          return h;
                      }
                  }
              }
              return null;
          }
      }
  
      /**
       * Wrap the handler if needed to fix its behavior.
       * When asked for a schema location, some simple handlers always return
       * the same url, whatever the asked location is.  This can lead to lots
       * of problems, so we need to verify and fix those behaviors.
       */
      private static NamespaceHandler wrapIfNeeded(final NamespaceHandler handler) {
          URL result = null;
          try {
              result = handler.getSchemaLocation("");
          } catch (Throwable t) {
              // Ignore
          }
          if (result != null) {
              LOGGER.warn("NamespaceHandler " + handler.getClass().getName() + " is behaving badly and should be fixed");
              final URL res = result;
              return new NamespaceHandler() {
                  final ConcurrentMap<String, Boolean> cache = new ConcurrentHashMap<String, Boolean>();
                  @Override
                  public URL getSchemaLocation(String s) {
                      URL url = handler.getSchemaLocation(s);
                      if (url != null && url.equals(res)) {
                          Boolean v, newValue;
                          Boolean valid = ((v = cache.get(s)) == null &&
                                  (newValue = isValidSchema(s, url)) != null &&
                                  (v = cache.putIfAbsent(s, newValue)) == null) ? newValue : v;
                          return valid ? url : null;
                      }
                      return url;
                  }
                  @Override
                  public Set<Class> getManagedClasses() {
                      return handler.getManagedClasses();
                  }
                  @Override
                  public Metadata parse(Element element, ParserContext parserContext) {
                      return handler.parse(element, parserContext);
                  }
                  @Override
                  public ComponentMetadata decorate(Node node, ComponentMetadata componentMetadata, ParserContext parserContext) {
                      return handler.decorate(node, componentMetadata, parserContext);
                  }
                  private boolean isValidSchema(String ns, URL url) {
                      try {
                          InputStream is = url.openStream();
                          try {
                              XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(is);
                              try {
                                  reader.nextTag();
                                  String nsuri = reader.getNamespaceURI();
                                  String name = reader.getLocalName();
                                  if ("http://www.w3.org/2001/XMLSchema".equals(nsuri) && "schema".equals(name)) {
                                      String target = reader.getAttributeValue(null, "targetNamespace");
                                      if (ns.equals(target)) {
                                          return true;
                                      }
                                  }
                              } finally {
                                  reader.close();
                              }
                          } finally {
                              is.close();
                          }
                      } catch (Throwable t) {
                          // Ignore
                      }
                      return false;
                  }
              };
          } else {
              return handler;
          }
      }
  
      public static class LRUMap<K, V> extends AbstractMap<K, V> {
  
          private final int bound;
          private final LinkedList<Entry<K, V>> entries = new LinkedList<Entry<K, V>>();
  
          private static class LRUEntry<K, V> implements Entry<K, V> {
              private final K key;
              private final V value;
  
              private LRUEntry(K key, V value) {
                  this.key = key;
                  this.value = value;
              }
  
              public K getKey() {
                  return key;
              }
  
              public V getValue() {
                  return value;
              }
  
              public V setValue(V value) {
                  throw new UnsupportedOperationException();
              }
          }
  
          private LRUMap(int bound) {
              this.bound = bound;
          }
  
          public V get(Object key) {
              if (key == null) {
                  throw new NullPointerException();
              }
              for (Entry<K, V> e : entries) {
                  if (e.getKey().equals(key)) {
                      entries.remove(e);
                      entries.addFirst(e);
                      return e.getValue();
                  }
              }
              return null;
          }
  
          public V put(K key, V value) {
              if (key == null) {
                  throw new NullPointerException();
              }
              V old = null;
              for (Entry<K, V> e : entries) {
                  if (e.getKey().equals(key)) {
                      entries.remove(e);
                      old = e.getValue();
                      break;
                  }
              }
              if (value != null) {
                  entries.addFirst(new LRUEntry<K, V>(key, value));
                  while (entries.size() > bound) {
                      entries.removeLast();
                  }
              }
              return old;
          }
  
          public Set<Entry<K, V>> entrySet() {
              return new AbstractSet<Entry<K, V>>() {
                  public Iterator<Entry<K, V>> iterator() {
                      return entries.iterator();
                  }
  
                  public int size() {
                      return entries.size();
                  }
              };
          }
      }
  
  }
  