/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.xml;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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.PriorityQueue;
import java.util.Set;
import java.util.Stack;
import javax.xml.namespace.QName;
import org.eclipse.emf.ecore.resource.URIHandler;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.util.XSDSchemaLocationResolver;
import org.eclipse.xsd.util.XSDSchemaLocator;
import org.geotools.util.Utilities;
import org.geotools.xml.impl.PicoMap;
import org.geotools.xs.XSConfiguration;
import org.picocontainer.ComponentAdapter;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.defaults.DefaultPicoContainer;
import org.picocontainer.defaults.DuplicateComponentKeyRegistrationException;
/**
* Responsible for configuring a parser runtime environment.
*
* <p>
* Implementations have the following responsibilites:
*
* <ul>
* <li>Configuration of bindings.
* <li>Configuration of context used by bindings.
* <li>Supplying specialized handlers for looking up schemas.
* <li>Supplying specialized handlers for parsing schemas.
* <li>Declaring dependencies on other configurations
* </ul>
* </p>
* <h3>Dependencies</h3>
* <p>
* Configurations have dependencies on one another, that result from teh fact that
* one schema imports another. Configuration dependencies are transitive.
* Each configuration should declare all dependencies in
* the constructor using the {@link #addDependency(Configuration)} method.
* <code>
* <pre>
* class MyConfiguration extends Configuration {
* public MyConfiguration() {
* super();
*
* addDependency( new FooConfiguration() );
* addDependency( new BarConfiguration() );
* }
* ...
* }
* </pre>
* </code>
* </p>
* <h3>Binding Configuration</h3>
* <p>
* In able for a particular binding to be found during a parse, the
* configuration must first populate a container with said binding. This
* can be done by returning the appropriate instance of
* {@link org.geotools.xml.BindingConfiguration} in {@link #getBindingConfiguration()}:
* <pre>
* <code>
* BindingConfiguration getBindingConfiguration() {
* return new MyBindingConfiguration();
* }
* </code>
* </pre>
*
* Instances of type {@link org.geotools.xml.BindingConfiguration} are used to
* populate a container with all the bindings from a particular schema.
* </p>
*
* <h3>Context Configuration</h3>
* <p>
* Many bindings have dependencies on other types of objects. The pattern used
* to satisfy these dependencies is known as <b>Constructor Injection</b>. Which
* means that any dependencies a binding has is passed to it in its constructor.
* For instance, the following binding has a dependency on java.util.List.
*
* <pre>
* <code>
* class MyBinding implements SimpleBinding {
*
* List list;
*
* public MyBinding(List list) {
* this.list = list;
* }
* }
* </code>
* </pre>
*
* Before a binding can be created, the container in which it is housed in must
* be able to satisfy all of its dependencies. It is the responsibility of the
* configuration to statisfy this criteria. This is known as configuring the
* binding context. The following is a suitable configuration for the above
* binding.
*
* <pre>
* <code>
* class MyConfiguration extends Configuration {
* ....
* void configureContext(MutablePicoContainer container) {
* container.registerComponentImplementation(ArrayList.class);
* }
* }
* </code>
* </pre>
*
*
* <h3>Schema Resolution</h3>
* <p>
* XML instance documents often contain schema uri references that are invalid
* with respect to the parser, or non-existant. A configuration can supply
* specialized look up classes to prevent the parser from following an
* invalid uri and prevent any errors that may occur as a result.
* </p>
* <p>
* An instance of {@link org.eclipse.xsd.util.XSDSchemaLocationResolver} can be
* used to override a schemaLocation referencing another schema. This can be useful
* when the entity parsing an instance document stores schemas in a location
* unkown to the entity providing hte instance document.
* </p>
*
* <p>
* An instance of {@link org.eclipse.xsd.util.XSDSchemaLocator} can be used
* to provide an pre-parsed schema and prevent the parser from parsing a
* schemaLocation manually. This can be useful when an instance document does
* not supply a schemaLocation for the targetNamespace of the document.
* <pre>
* <code>
* class MyConfiguration implements Configuration {
*
* XSDSchemaLocationResolver getSchemaLocationResolver() {
* return new MySchemaLocationResolver();
* }
*
* XSDSchemaLocator getSchemaLocator() {
* return new MySchemaLocator();
* }
* }
* </code>
* </pre>
*
* </p>
* <p>
* The XSDSchemaLocator and XSDSchemaLocationResolver implementations are used
* in a couple of scenarios. The first is when the <b>schemaLocation</b>
* attribute of the root element of the instance document is being parsed.
* The schemaLocation attribute has the form:
*
* <pre>
* <code>
* schemaLocation="namespace location namespace location ..."
* </code>
* </pre>
*
* In which (namespace,location) tuples are listed. For each each namespace
* encountered when parsing the schemaLocation attribute, an appropriate
* resolver / locator is looked up. If an override is not aviable, the framework
* attempts to resolve the location part of the tuple into a schema.
*
* The second scenario occurs when the parsing of a schema encounters an
* <b>import</b> or an <b>include</b> element. These elements have the form:
*
* <pre>
* <code>
* <import namespace="" schemaLocation=""/>
* </code>
* </pre>
*
* and:
*
* <pre>
* <code>
* <include schemaLocation="">
* </code>
* </pre>
*
* respectivley. Similar to above, the schemaLocation (and namespace in the
* case of an import) are used to find an override. If not found they are
* resolved directly.
* </p>
*
* @author Justin Deoliveira,Refractions Research Inc.,jdeolive@refractions.net
* @see org.geotools.xml.BindingConfiguration
*
*
*
* @source $URL$
*/
public abstract class Configuration {
/**
* XSD instance
*/
private final XSD xsd;
/**
* List of configurations depended on.
*/
private final List dependencies;
/**
* List of parser properties.
*/
private final Set properties;
/**
* Internal context
*/
private final MutablePicoContainer context;
/**
* Creates a new configuration.
* <p>
* Any dependent schemas should be added in sublcass constructor. The xml schema
* dependency does not have to be added.
* </p>
*
*/
public Configuration(XSD xsd) {
this.xsd = xsd;
dependencies = Collections.synchronizedList(new ArrayList());
//bootstrap check
if (!(this instanceof XSConfiguration)) {
dependencies.add(new XSConfiguration());
}
properties = Collections.synchronizedSet(new HashSet());
context = new DefaultPicoContainer();
}
/**
* The XSD instance representing the schema for which the schema works
* against.
*/
public XSD getXSD() {
return xsd;
}
/**
* @return a list of direct dependencies of the configuration.
*
*/
public final List /*<Configuration>*/ getDependencies() {
return dependencies;
}
/**
* Returns a list of parser properties to set.
* <p>
* To set a parser property:
* <pre>
* Configuration configuration = ...
* configuration.getProperties().add( Parser.Properties.... );
* </pre>
* </p>
* <p>
* Beware this class is not thread safe so take the needed precautions
* when using the list returned by this method.
* </p>
* @return A list of hte set parser properties.
*/
public final Set /*<QName>*/ getProperties() {
return properties;
}
/**
* Searches the configuration and all dependent configuration for the speciifed property.
*
*/
public final boolean hasProperty( QName property ) {
for ( Iterator c = allDependencies().iterator(); c.hasNext(); ) {
Configuration configuration = (Configuration) c.next();
if ( configuration.getProperties().contains( property ) ) {
return true;
}
}
return false;
}
/**
* Returns all dependencies in the configuration dependency tree.
* <p>
* The return list contains no duplicates.
* </p>
* @return All dependencies in teh configuration dependency tree.
*/
public final List allDependencies() {
LinkedList unpacked = new LinkedList();
Stack stack = new Stack();
stack.push(this);
while (!stack.isEmpty()) {
Configuration c = (Configuration) stack.pop();
if (!unpacked.contains(c)) {
unpacked.addFirst(c);
stack.addAll(c.getDependencies());
}
}
if (unpacked.size() < 2) {
return unpacked;
}
//create a graph of the dependencies
DepGraph g = new DepGraph();
for (Configuration c : (List<Configuration>)unpacked) {
for (Configuration d : (List<Configuration>)c.getDependencies()) {
g.addEdge(c, d);
}
}
PriorityQueue<DepNode> q = new PriorityQueue<DepNode>(g.nodes.size(), new Comparator<DepNode>() {
public int compare(DepNode o1, DepNode o2) {
return Integer.valueOf(o1.outgoing().size()).compareTo(o2.outgoing().size());
}
});
for (DepNode n : g.nodes.values()) {
q.add(n);
}
unpacked = new LinkedList();
while(!q.isEmpty()) {
DepNode n = q.remove();
if (n.outgoing().size() != 0) {
throw new IllegalStateException();
}
unpacked.add(n.config);
for (DepNode i : n.incoming()) {
g.removeEdge(i.config, n.config);
/*
* PriorityQueue.remove(Object) is broken in Java 5
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6207984
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6268068
*/
q.removeAll(Collections.singletonList(i));;
q.add(i);
}
}
return unpacked;
}
/**
* Returns the first dependency of this configuration of the specified type.
* @since 8.0
*/
public <C extends Configuration> C getDependency(Class<C> clazz) {
//first try straight dependencies
for (Configuration dep : (List<Configuration>)getDependencies()) {
if (clazz.isInstance(dep)) {
return (C) dep;
}
}
//fall back to all
for (Configuration dep : (List<Configuration>)allDependencies()) {
if (clazz.isInstance(dep)) {
return (C) dep;
}
}
return null;
}
/**
* Adds a dependent configuration.
* <p>
* This method should only be called from the constructor.
* </p>
* @param dependency
*/
protected void addDependency(Configuration dependency) {
if (dependencies.contains(dependency)) {
return;
}
dependencies.add(dependency);
}
/**
* @return The namespace of the configuration schema.
*/
public final String getNamespaceURI() {
return getXSD().getNamespaceURI();
}
/**
* Returns the url to the file definiing hte schema.
* <p>
* For schema which are defined by multiple files, this method should return the base schema
* which includes all other files that define the schema.
* </p>
* @deprecated use {@link XSD#getSchemaLocation()}.
*/
public final String getSchemaFileURL() {
return getXSD().getSchemaLocation();
}
/**
* Returns a schema location resolver instance used to override schema location
* uri's encountered in an instance document.
* <p>
* This method should be overridden to return such an instance. The default
* implemntation returns <code>null</code>
* </p>
* @return The schema location resolver, or <code>null</code>
* @deprecated
*
*/
public final XSDSchemaLocationResolver getSchemaLocationResolver() {
return new SchemaLocationResolver(xsd);
}
/**
* Returns a schema locator, used to create imported and included schemas
* when parsing an instance document.
* <p>
* This method may be overriden to return such an instance. The default
* delegates to {@link #createSchemaLocator()} to and caches the restult. This method
* may return <code>null</code> to indicate that no such locator should be used.
* </p>
* @return The schema locator, or <code>null</code>
* @deprecated
*
*/
public final XSDSchemaLocator getSchemaLocator() {
return new SchemaLocator(xsd);
}
/**
* Convenience method for creating an instance of the schema for this configuration.
*
* @return The schema for this configuration.
* @deprecated use {@link #getXSD()} and {@link XSD#getSchema()}.
*/
public XSDSchema schema() {
try {
return getXSD().getSchema();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns an internal context which is copied into the runtime context
* while parsing.
* <p>
* This context is provided to allow for placing values in the parsing context
* without having to sublcass.
* </p>
* @return The context.
*/
public final MutablePicoContainer getContext() {
return context;
}
/**
* Configures a container which houses all the bindings used during a parse.
*
* @param container The container housing the binding objects.
* @deprecated use {@link #setupBindings()}.
*/
public final MutablePicoContainer setupBindings(MutablePicoContainer container) {
//configure bindings of all dependencies
for (Iterator d = allDependencies().iterator(); d.hasNext();) {
Configuration dependency = (Configuration) d.next();
dependency.registerBindings(container);
}
//call template method, create a new container to allow subclass to override bindings
container = container.makeChildContainer();
configureBindings(container);
return container;
}
/**
* Creates the map of QName to Binding which is used during parsing to attach
* bindinds to an element,attribute, or type.
*
* @return A map of Qname,[Class|Object]
*/
public final Map setupBindings() {
HashMap bindings = new HashMap();
//wrap the binding map up in a pico container for backwards compatability
// with old api which registered bindings in a pico container
PicoMap container = new PicoMap(bindings);
//configure bindings of all dependencies
for (Iterator d = allDependencies().iterator(); d.hasNext();) {
Configuration dependency = (Configuration) d.next();
dependency.registerBindings(bindings);
//call old api
dependency.registerBindings((MutablePicoContainer)container);
}
for (Iterator d = allDependencies().iterator(); d.hasNext();) {
Configuration dependency = (Configuration) d.next();
dependency.configureBindings(bindings);
//call old api
dependency.configureBindings((MutablePicoContainer)container);
}
return bindings;
}
/**
* Prepares a parser instance for use with this Configuration instance and
* all of its dependencies.
*
* @since 2.7
*/
public final void setupParser(Parser parser) {
for (Iterator it = allDependencies().iterator(); it.hasNext();) {
Configuration dep = (Configuration) it.next();
dep.configureParser(parser);
}
}
/**
* Prepares a encoder instance for use with this Configuration instance and
* all of its dependencies.
*
* @since 2.7
*/
public final void setupEncoder(Encoder encoder) {
for (Iterator it = allDependencies().iterator(); it.hasNext();) {
Configuration dep = (Configuration) it.next();
dep.configureEncoder(encoder);
}
}
/**
* Registers the bindings for the configuration.
* <p>
* This method is intended to provide the default bindings for a configuration
* and is intended to be subclassed by client code. Client code should use
* {@link #configureBindings(MutablePicoContainer)} .
* </p>
* Subclasses should mark this method as final after implementing.
* </p>
*
* @param container Container containing all bindings, keyed by {@link QName}.
*
* @deprecated use {@link #registerBindings(Map)}.
*/
protected void registerBindings(MutablePicoContainer container) {
//do nothing, in the case where the subclass has overridden the config
// will recognize and apapt this method to #registerBindings(Map)
// accordingly (see #setupBindings()}
}
/**
* Registers the bindings for the configuration.
* <p>
* This method is intended to provide the "default" bindings for a configuration
* and is not intended to be subclassed by client code. Client code should use
* {@link #configureBindings(MutablePicoContainer)} to override/remove/add new
* bindings on teh fly.
* </p>
* <p>
* The key of the <tt>bindings</tt> map is of type {@link QName}. The value
* can be class, or an instance. In the case of a class, the binding will be
* instantiated by the parser at runtime. In the instance case the binding
* will be used as is.
* </p>
*/
protected void registerBindings(Map/*<QName,Object>*/ bindings) {
}
/**
* Template method allowing subclass to override any bindings.
*
* @param container Container containing all bindings, keyed by {@link QName}.
* @deprecated use {@link #configureBindings(Map)}.
*/
protected void configureBindings(MutablePicoContainer container) {
//do nothing
}
/**
* Template method allowing subclass to override any bindings.
*
* @param bindings Map containing all bindings, keyed by {@link QName}.
*/
protected void configureBindings(Map bindings) {
//do nothing
}
/**
* Configures the root context to be used when parsing elements.
*
* @param container The container representing the context.
*/
public final MutablePicoContainer setupContext(MutablePicoContainer container) {
//configure bindings of all dependencies
List dependencies = allDependencies();
for (Iterator d = dependencies.iterator(); d.hasNext();) {
Configuration dependency = (Configuration) d.next();
//throw locator and location resolver into context
XSDSchemaLocationResolver resolver = dependency.getSchemaLocationResolver();
if (resolver != null) {
QName key = new QName(dependency.getNamespaceURI(), "schemaLocationResolver");
container.registerComponentInstance(key, resolver);
}
XSDSchemaLocator locator = dependency.getSchemaLocator();
if (locator != null) {
QName key = new QName(dependency.getNamespaceURI(), "schemaLocator");
container.registerComponentInstance(key, locator);
}
//set any parser properties
synchronized(dependency.getProperties()) {
for (QName property : (Set<QName>)dependency.getProperties()) {
try {
container.registerComponentInstance(property, property);
} catch (DuplicateComponentKeyRegistrationException e) {
//ok, ignore
}
}
}
//add any additional configuration, factories and such
// create a new container to allow configurations to override factories in dependant
// configurations
container = container.makeChildContainer();
dependency.configureContext(container);
}
//copy the internal context over
if (!context.getComponentAdapters().isEmpty()) {
container = container.makeChildContainer();
for (Iterator ca = context.getComponentAdapters().iterator(); ca.hasNext();) {
ComponentAdapter adapter = (ComponentAdapter) ca.next();
container.registerComponent(adapter);
}
}
return container;
}
/**
* Configures the root context to be used when parsing elements.
* <p>
* The context satisifies any depenencencies needed by a binding. This is
* often a factory used to create something.
* </p>
* <p>
* This method should be overriden. The default implementation does nothing.
* </p>
*
* @param container The container representing the context.
*/
protected void configureContext(MutablePicoContainer container) {
}
/**
* Configures the parser to be used with this configuration.
* <p>
* This method provides a callback for Configuration instances to configure
* the parser with whatever options they require.
* </p>
*/
protected void configureParser(Parser parser) {
}
/**
* Configures the encoder to be used with this configuration.
* <p>
* This method provides a callback for Configuration instances to configure
* the encoder with whatever options they require.
* </p>
*/
protected void configureEncoder(Encoder encoder) {
}
/**
* Equals override, equality is based soley on {@link #getNamespaceURI()}.
*/
public final boolean equals(Object obj) {
if (obj instanceof Configuration) {
Configuration other = (Configuration) obj;
return Utilities.equals(getNamespaceURI(), other.getNamespaceURI());
}
return false;
}
public final int hashCode() {
if (getNamespaceURI() != null) {
return getNamespaceURI().hashCode();
}
return 0;
}
static class DepGraph {
Map<Configuration,DepNode> nodes = new HashMap();
public void addEdge(Configuration from, Configuration to) {
DepNode src = addNode(from);
DepNode dst = addNode(to);
DepEdge dep = src.getEdge(dst);
if (dep != null) {
return;
}
//ensure two configurations not dependent on each other
if (dst.getEdge(src) != null) {
throw new IllegalArgumentException("Cycle between " + from + ", " + to );
}
dep = new DepEdge(src, dst);
src.edges.add(dep);
dst.edges.add(dep);
}
public void removeEdge(Configuration from, Configuration to) {
DepNode src = addNode(from);
DepNode dst = addNode(to);
DepEdge dep = src.getEdge(dst);
if (dep == null) {
throw new IllegalStateException("No such edge: " + from + "," + to);
}
src.edges.remove(dep);
dst.edges.remove(dep);
}
DepNode addNode(Configuration config) {
DepNode node = nodes.get(config);
if (node == null) {
node = new DepNode(config);
nodes.put(config, node);
}
return node;
}
}
static class DepNode {
Configuration config;
List<DepEdge> edges = new ArrayList();
DepNode(Configuration config) {
this.config = config;
}
DepEdge getEdge(DepNode node) {
for (DepEdge edge: edges) {
if (edge.src == this && edge.dst == node) {
return edge;
}
}
return null;
}
public List<DepNode> incoming() {
List<DepNode> incoming = new ArrayList();
for (DepEdge edge : edges) {
if (edge.dst == this) {
incoming.add(edge.src);
}
}
return incoming;
}
public List<DepNode> outgoing() {
List<DepNode> outgoing = new ArrayList();
for (DepEdge edge : edges) {
if (edge.src == this) {
outgoing.add(edge.dst);
}
}
return outgoing;
}
@Override
public String toString() {
return config.toString();
}
}
static class DepEdge {
DepNode src, dst;
DepEdge(DepNode src, DepNode dst) {
this.src = src;
this.dst = dst;
}
@Override
public String toString() {
return "[" + src.toString() + ", " + dst.toString() + "]";
}
}
}