| /* |
| * 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.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.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$, $Date$ |
| */ |
| 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, Set<NamespaceHandler>> handlers; |
| private final ServiceTracker tracker; |
| private final Map<Map<URI, NamespaceHandler>, Reference<Schema>> schemas = new LRUMap<Map<URI, NamespaceHandler>, Reference<Schema>>(10); |
| private SchemaFactory schemaFactory; |
| private List<NamespaceHandlerSetImpl> sets; |
| |
| public NamespaceHandlerRegistryImpl(BundleContext bundleContext) { |
| this.bundleContext = bundleContext; |
| handlers = new HashMap<URI, Set<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, 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, Map properties) { |
| List<URI> namespaces = getNamespaces(properties); |
| for (URI uri : namespaces) { |
| Set<NamespaceHandler> h = handlers.get(uri); |
| if (h == null) { |
| h = new HashSet<NamespaceHandler>(); |
| handlers.put(uri, h); |
| } |
| if (h.add(handler)) { |
| 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) { |
| Set<NamespaceHandler> h = handlers.get(uri); |
| if (h == null || !h.remove(handler)) { |
| 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()) { |
| if (key.equals(handlers)) { |
| 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()])); |
| 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) { |
| 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) { |
| handlers.put(ns, h); |
| return h; |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| 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(); |
| } |
| }; |
| } |
| } |
| |
| } |