/* * 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.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; } /** * 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() + "]"; } } }