blob: e184a249deb0848246aeceaae97467ff6b60e7f6 [file] [log] [blame]
/*
* 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.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.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
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.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.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 bundle {}/{}",
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()) {
return URI.create(baseURI).resolve(sId).toString();
}
}
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, h);
return h;
}
}
}
return null;
}
}
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();
}
};
}
}
}