/* * 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 java.lang.ref.Reference; import java.lang.ref.SoftReference; 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.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.HashSet; import java.io.IOException; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.transform.stream.StreamSource; import javax.xml.transform.Source; import javax.xml.XMLConstants; import org.apache.aries.blueprint.NamespaceHandler; import org.apache.aries.blueprint.container.NamespaceHandlerRegistry; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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: 1066828 $, $Date: 2011-02-03 15:13:58 +0000 (Thu, 03 Feb 2011) $ */ public class NamespaceHandlerRegistryImpl implements NamespaceHandlerRegistry, ServiceTrackerCustomizer { public static final URI BLUEPRINT_NAMESPACE = URI.create("http://www.osgi.org/xmlns/blueprint/v1.0.0"); public static final String NAMESPACE = "osgi.service.blueprint.namespace"; private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceHandlerRegistryImpl.class); private final BundleContext bundleContext; private final Map<URI, Map<NamespaceHandler,ServiceReference<NamespaceHandler>>> handlers; private final ServiceTracker tracker; private final Map<Map<URI, NamespaceHandler>, Reference<Schema>> schemas = new LRUMap<Map<URI, NamespaceHandler>, Reference<Schema>>(10); private final List<NamespaceHandlerSetImpl> sets; public NamespaceHandlerRegistryImpl(BundleContext bundleContext) { this.bundleContext = bundleContext; handlers = new HashMap<URI, Map<NamespaceHandler, ServiceReference<NamespaceHandler>>>(); sets = new ArrayList<NamespaceHandlerSetImpl>(); 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, reference, props); } catch (Exception e) { LOGGER.warn("Error registering NamespaceHandler", e); } }else{ LOGGER.warn("Error resolving NamespaceHandler, null Service obtained from tracked ServiceReference {} for bundle {}, ver {}", new Object[]{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 { 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 synchronized void registerHandler(NamespaceHandler handler, ServiceReference<NamespaceHandler> ref, Map properties) { List<URI> namespaces = getNamespaces(properties); for (URI uri : namespaces) { Map<NamespaceHandler, ServiceReference<NamespaceHandler>> h = handlers.get(uri); if (h == null) { h = new HashMap<NamespaceHandler, ServiceReference<NamespaceHandler>>(); handlers.put(uri, h); } if (h.put(handler, ref) == null) { for (NamespaceHandlerSetImpl s : sets) { s.registerHandler(uri, handler); } } } } public synchronized void unregisterHandler(NamespaceHandler handler, Map properties) { List<URI> namespaces = getNamespaces(properties); for (URI uri : namespaces) { Map<NamespaceHandler, ServiceReference<NamespaceHandler>> h = handlers.get(uri); if (h == null || h.remove(handler) == null) { continue; } 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 synchronized NamespaceHandlerSet getNamespaceHandlers(Set<URI> uris, Bundle bundle) { NamespaceHandlerSetImpl s = new NamespaceHandlerSetImpl(uris, bundle); sets.add(s); return s; } public void destroy() { tracker.close(); } public synchronized Schema getSchema(Map<URI, NamespaceHandler> handlers) throws IOException, SAXException { Schema schema = null; // 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 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) { schema = schemas.get(key).get(); break; } } if (schema == null) { List<StreamSource> schemaSources = new ArrayList<StreamSource>(); try { schemaSources.add(new StreamSource(getClass().getResourceAsStream("/org/apache/aries/blueprint/blueprint.xsd"))); // Create a schema for all namespaces known at this point // It will speed things as it can be reused for all other blueprint containers for (URI ns : handlers.keySet()) { URL url = handlers.get(ns).getSchemaLocation(ns.toString()); if (url == null) { LOGGER.warn("No URL is defined for schema " + ns + ". This schema will not be validated"); } else { schemaSources.add(new StreamSource(url.openStream())); } } schema = getSchemaFactory().newSchema(schemaSources.toArray(new Source[schemaSources.size()])); // 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)); } finally { for (StreamSource s : schemaSources) { try { s.getInputStream().close(); } catch (IOException e) { // Ignore } } } } return schema; } protected synchronized void removeSchemasFor(NamespaceHandler handler) { 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 SchemaFactory getSchemaFactory() { SchemaFactory schemaFactory = null; if (schemaFactory == null) { schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); } return schemaFactory; } protected class NamespaceHandlerSetImpl implements NamespaceHandlerSet { private final Map<Listener, Boolean> listeners; private final Bundle bundle; private final Set<URI> namespaces; private final Map<URI, NamespaceHandler> handlers; private Schema schema; public NamespaceHandlerSetImpl(Set<URI> namespaces, Bundle bundle) { this.listeners = new HashMap<Listener, Boolean>(); this.namespaces = namespaces; this.bundle = bundle; handlers = new HashMap<URI, NamespaceHandler>(); for (URI ns : namespaces) { findCompatibleNamespaceHandler(ns); } } 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 { if (!isComplete()) { throw new IllegalStateException("NamespaceHandlerSet is not complete"); } if (schema == null) { schema = NamespaceHandlerRegistryImpl.this.getSchema(handlers); } return schema; } public synchronized void addListener(Listener listener) { listeners.put(listener, Boolean.TRUE); } public synchronized void removeListener(Listener listener) { listeners.remove(listener); } public void destroy() { 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.keySet()) { 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.keySet()) { try { listener.namespaceHandlerUnregistered(uri); } catch (Throwable t) { LOGGER.debug("Unexpected exception when notifying a NamespaceHandler listener", t); } } } } private NamespaceHandler findCompatibleNamespaceHandler(URI ns) { Map<NamespaceHandler, ServiceReference<NamespaceHandler>> nsmap = NamespaceHandlerRegistryImpl.this.handlers.get(ns); if (nsmap == null) return null; Collection<NamespaceHandler> candidates = nsmap.keySet(); BundleWiring wiring = bundle.adapt(BundleWiring.class); List<BundleWire> wires = wiring.getRequiredWires("org.apache.aries.blueprint.NamespaceHandler"); for (BundleWire wire : wires) { String wireNs = (String) wire.getCapability().getAttributes().get("osgi.service.blueprint.namespace"); try { if (wireNs != null && ns.equals(new URI(wireNs))) { candidates = new ArrayList<NamespaceHandler>(candidates); Iterator<NamespaceHandler> it = candidates.iterator(); while (it.hasNext()) { NamespaceHandler handler = it.next(); ServiceReference<NamespaceHandler> ref = nsmap.get(handler); // different provider bundle if (!!!ref.getBundle().equals(wire.getProviderWiring().getBundle())) { it.remove(); } else { Version capabilityVersion = (Version) wire.getCapability().getAttributes().get("version"); Version serviceVersion = (Version) ref.getProperty("version"); // version mismatch, same provider but a different version, so the provider obviously has // multiple competing versions to offer if ((capabilityVersion != null && !!!capabilityVersion.equals(serviceVersion)) || (capabilityVersion == null && serviceVersion != null)) { it.remove(); } } } } } catch (URISyntaxException e) { LOGGER.error("Non-URI blueprint namespace {}", wireNs); } } Iterator<NamespaceHandler> it = candidates.iterator(); while (it.hasNext()) { NamespaceHandler h = it.next(); 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) it.remove(); } // pick highest version NamespaceHandler best = null; Version bestVersion = null; for (NamespaceHandler cand : candidates) { ServiceReference<NamespaceHandler> ref = nsmap.get(cand); Version version = (Version) ref.getProperty("version"); if (version == null) version = new Version("0.0.0"); if (best == null || version.compareTo(bestVersion) > 0) { best = cand; bestVersion = version; } } if (best != null) { handlers.put(ns, best); } return best; } } protected static Map<URI, NamespaceHandler> findHandlers(Map<URI, Set<NamespaceHandler>> allHandlers, Set<URI> namespaces, Bundle bundle) { Map<URI, NamespaceHandler> handlers = new HashMap<URI, NamespaceHandler>(); Map<URI, Set<NamespaceHandler>> candidates = new HashMap<URI, Set<NamespaceHandler>>(); // Populate initial candidates for (URI ns : namespaces) { Set<NamespaceHandler> h = new HashSet<NamespaceHandler>(); if (allHandlers.get(ns) != null) { h.addAll(allHandlers.get(ns)); } candidates.put(ns, h); } // Exclude directly incompatible handlers for (URI ns : namespaces) { for (Iterator<NamespaceHandler> it = candidates.get(ns).iterator(); it.hasNext();) { NamespaceHandler h = it.next(); 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()); } catch (Throwable t) { clb = null; } if (clb != cl) { compat = false; break; } } } if (!compat) { it.remove(); } } } // TODO: do we need to check if there are incompatibilities between namespaces? // Pick the first ones for (URI ns : namespaces) { Set<NamespaceHandler> h = candidates.get(ns); if (!h.isEmpty()) { handlers.put(ns, h.iterator().next()); } } return handlers; } 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(); } }; } } }