001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.xml;
017:
018: import java.util.ArrayList;
019: import java.util.Iterator;
020: import java.util.LinkedList;
021: import java.util.List;
022: import java.util.Stack;
023:
024: import javax.xml.namespace.QName;
025:
026: import org.eclipse.xsd.XSDSchema;
027: import org.eclipse.xsd.util.XSDSchemaLocationResolver;
028: import org.eclipse.xsd.util.XSDSchemaLocator;
029: import org.geotools.resources.Utilities;
030: import org.geotools.xs.XSConfiguration;
031: import org.picocontainer.MutablePicoContainer;
032: import org.picocontainer.defaults.DuplicateComponentKeyRegistrationException;
033:
034: /**
035: * Responsible for configuring a parser runtime environment.
036: *
037: * <p>
038: * Implementations have the following responsibilites:
039: *
040: * <ul>
041: * <li>Configuration of bindings.
042: * <li>Configuration of context used by bindings.
043: * <li>Supplying specialized handlers for looking up schemas.
044: * <li>Supplying specialized handlers for parsing schemas.
045: * <li>Declaring dependencies on other configurations
046: * </ul>
047: * </p>
048: * <h3>Dependencies</h3>
049: * <p>
050: * Configurations have dependencies on one another, that result from teh fact that
051: * one schema imports another. Configuration dependencies are transitive.
052: * Each configuration should declare all dependencies in
053: * the constructor using the {@link #addDependency(Configuration)} method.
054: * <code>
055: * <pre>
056: * class MyConfiguration extends Configuration {
057: * public MyConfiguration() {
058: * super();
059: *
060: * addDependency( new FooConfiguration() );
061: * addDependency( new BarConfiguration() );
062: * }
063: * ...
064: * }
065: * </pre>
066: * </code>
067: * </p>
068: * <h3>Binding Configuration</h3>
069: * <p>
070: * In able for a particular binding to be found during a parse, the
071: * configuration must first populate a container with said binding. This
072: * can be done by returning the appropriate instance of
073: * {@link org.geotools.xml.BindingConfiguration} in {@link #getBindingConfiguration()}:
074: * <pre>
075: * <code>
076: * BindingConfiguration getBindingConfiguration() {
077: * return new MyBindingConfiguration();
078: * }
079: * </code>
080: * </pre>
081: *
082: * Instances of type {@link org.geotools.xml.BindingConfiguration} are used to
083: * populate a container with all the bindings from a particular schema.
084: * </p>
085: *
086: * <h3>Context Configuration</h3>
087: * <p>
088: * Many bindings have dependencies on other types of objects. The pattern used
089: * to satisfy these dependencies is known as <b>Constructor Injection</b>. Which
090: * means that any dependencies a binding has is passed to it in its constructor.
091: * For instance, the following binding has a dependency on java.util.List.
092: *
093: * <pre>
094: * <code>
095: * class MyBinding implements SimpleBinding {
096: *
097: * List list;
098: *
099: * public MyBinding(List list) {
100: * this.list = list;
101: * }
102: * }
103: * </code>
104: * </pre>
105: *
106: * Before a binding can be created, the container in which it is housed in must
107: * be able to satisfy all of its dependencies. It is the responsibility of the
108: * configuration to statisfy this criteria. This is known as configuring the
109: * binding context. The following is a suitable configuration for the above
110: * binding.
111: *
112: * <pre>
113: * <code>
114: * class MyConfiguration extends Configuration {
115: * ....
116: * void configureContext(MutablePicoContainer container) {
117: * container.registerComponentImplementation(ArrayList.class);
118: * }
119: * }
120: * </code>
121: * </pre>
122: *
123: *
124: * <h3>Schema Resolution</h3>
125: * <p>
126: * XML instance documents often contain schema uri references that are invalid
127: * with respect to the parser, or non-existant. A configuration can supply
128: * specialized look up classes to prevent the parser from following an
129: * invalid uri and prevent any errors that may occur as a result.
130: * </p>
131: * <p>
132: * An instance of {@link org.eclipse.xsd.util.XSDSchemaLocationResolver} can be
133: * used to override a schemaLocation referencing another schema. This can be useful
134: * when the entity parsing an instance document stores schemas in a location
135: * unkown to the entity providing hte instance document.
136: * </p>
137: *
138: * <p>
139: * An instance of {@link org.eclipse.xsd.util.XSDSchemaLocator} can be used
140: * to provide an pre-parsed schema and prevent the parser from parsing a
141: * schemaLocation manually. This can be useful when an instance document does
142: * not supply a schemaLocation for the targetNamespace of the document.
143: * <pre>
144: * <code>
145: * class MyConfiguration implements Configuration {
146: *
147: * XSDSchemaLocationResolver getSchemaLocationResolver() {
148: * return new MySchemaLocationResolver();
149: * }
150: *
151: * XSDSchemaLocator getSchemaLocator() {
152: * return new MySchemaLocator();
153: * }
154: * }
155: * </code>
156: * </pre>
157: *
158: * </p>
159: * <p>
160: * The XSDSchemaLocator and XSDSchemaLocationResolver implementations are used
161: * in a couple of scenarios. The first is when the <b>schemaLocation</b>
162: * attribute of the root element of the instance document is being parsed.
163: * The schemaLocation attribute has the form:
164: *
165: * <pre>
166: * <code>
167: * schemaLocation="namespace location namespace location ..."
168: * </code>
169: * </pre>
170: *
171: * In which (namespace,location) tuples are listed. For each each namespace
172: * encountered when parsing the schemaLocation attribute, an appropriate
173: * resolver / locator is looked up. If an override is not aviable, the framework
174: * attempts to resolve the location part of the tuple into a schema.
175: *
176: * The second scenario occurs when the parsing of a schema encounters an
177: * <b>import</b> or an <b>include</b> element. These elements have the form:
178: *
179: * <pre>
180: * <code>
181: * <import namespace="" schemaLocation=""/>
182: * </code>
183: * </pre>
184: *
185: * and:
186: *
187: * <pre>
188: * <code>
189: * <include schemaLocation="">
190: * </code>
191: * </pre>
192: *
193: * respectivley. Similar to above, the schemaLocation (and namespace in the
194: * case of an import) are used to find an override. If not found they are
195: * resolved directly.
196: * </p>
197: *
198: * @author Justin Deoliveira,Refractions Research Inc.,jdeolive@refractions.net
199: * @see org.geotools.xml.BindingConfiguration
200: */
201: public abstract class Configuration {
202:
203: /**
204: * List of configurations depended on.
205: */
206: private List dependencies;
207:
208: /**
209: * Holds the schema locator instance for this configuration, which
210: * in turn caches the parsed XSDSchema
211: */
212: private XSDSchemaLocator schemaLocator;
213:
214: /**
215: * List of parser properties.
216: */
217: private List properties;
218:
219: /**
220: * Creates a new configuration.
221: * <p>
222: * Any dependent schemas should be added in sublcass constructor. The xml schema
223: * dependency does not have to be added.
224: * </p>
225: *
226: */
227: public Configuration() {
228: dependencies = new ArrayList();
229:
230: //bootstrap check
231: if (!(this instanceof XSConfiguration)) {
232: dependencies.add(new XSConfiguration());
233: }
234:
235: properties = new ArrayList();
236: }
237:
238: /**
239: * @return a list of direct dependencies of the configuration.
240: *
241: */
242: public final List/*<Configuration>*/getDependencies() {
243: return dependencies;
244: }
245:
246: /**
247: * Returns a list of parser properties to set.
248: * <p>
249: * To set a parser property:
250: * <pre>
251: * Configuration configuration = ...
252: * configuration.getProperties().add( Parser.Properties.... );
253: * </pre>
254: * </p>
255: * @return A list of hte set parser properties.
256: */
257: public final List/*<QName>*/getProperties() {
258: return properties;
259: }
260:
261: /**
262: * Returns all dependencies in the configuration dependency tree.
263: * <p>
264: * The return list contains no duplicates.
265: * </p>
266: * @return All dependencies in teh configuration dependency tree.
267: */
268: public final List allDependencies() {
269:
270: LinkedList unpacked = new LinkedList();
271:
272: Stack stack = new Stack();
273: stack.push(this );
274:
275: while (!stack.isEmpty()) {
276: Configuration c = (Configuration) stack.pop();
277: if (!unpacked.contains(c)) {
278: unpacked.addFirst(c);
279: stack.addAll(c.getDependencies());
280: }
281: }
282:
283: return unpacked;
284: }
285:
286: /**
287: * Adds a dependent configuration.
288: * <p>
289: * This method should only be called from the constructor.
290: * </p>
291: * @param dependency
292: */
293: protected void addDependency(Configuration dependency) {
294: if (dependencies.contains(dependency))
295: return;
296:
297: dependencies.add(dependency);
298: }
299:
300: /**
301: * @return The namespace of the configuration schema.
302: */
303: abstract public String getNamespaceURI();
304:
305: /**
306: * Returns the url to the file definiing hte schema.
307: * <p>
308: * For schema which are defined by multiple files, this method should return the base schema
309: * which includes all other files that define the schema.
310: * </p>
311: * TODO: rename this to getSchemaLocation()
312: */
313: abstract public String getSchemaFileURL();
314:
315: /**
316: * @return The binding set for types, elements, attributes of the configuration schema.
317: */
318: abstract public BindingConfiguration getBindingConfiguration();
319:
320: /**
321: * Returns a schema location resolver instance used to override schema location
322: * uri's encountered in an instance document.
323: * <p>
324: * This method should be overridden to return such an instance. The default
325: * implemntation returns <code>null</code>
326: * </p>
327: * @return The schema location resolver, or <code>null</code>
328: */
329: public XSDSchemaLocationResolver getSchemaLocationResolver() {
330: return new SchemaLocationResolver(this );
331: }
332:
333: /**
334: * Returns a schema locator, used to create imported and included schemas
335: * when parsing an instance document.
336: * <p>
337: * This method may be overriden to return such an instance. The default
338: * delegates to {@link #createSchemaLocator()} to and caches the restult. This method
339: * may return <code>null</code> to indicate that no such locator should be used.
340: * </p>
341: * @return The schema locator, or <code>null</code>
342: */
343: public XSDSchemaLocator getSchemaLocator() {
344: if (schemaLocator == null) {
345: synchronized (this ) {
346: if (schemaLocator == null) {
347: schemaLocator = createSchemaLocator();
348: }
349: }
350: }
351: return schemaLocator;
352: }
353:
354: /**
355: * Template method for creating a new instance of {@link XSDSchemaLocator}.
356: * <p>
357: * Subclasses may override this method, the default implementation returns
358: * a new instance of {@link SchemaLocator}.
359: * </p>
360: *
361: */
362: protected XSDSchemaLocator createSchemaLocator() {
363: return new SchemaLocator(this );
364: }
365:
366: /**
367: * Convenience method for creating an instance of the schema for this configuration.
368: *
369: * @return The schema for this configuration.
370: */
371: public XSDSchema schema() {
372: return getSchemaLocator().locateSchema(null, getNamespaceURI(),
373: null, null);
374: }
375:
376: /**
377: * Configures a container which houses all the bindings used during a parse.
378: *
379: * @param container The container housing the binding objects.
380: */
381: public final MutablePicoContainer setupBindings(
382: MutablePicoContainer container) {
383:
384: //configure bindings of all dependencies
385: for (Iterator d = allDependencies().iterator(); d.hasNext();) {
386: Configuration dependency = (Configuration) d.next();
387:
388: BindingConfiguration bindings = dependency
389: .getBindingConfiguration();
390: if (bindings != null)
391: bindings.configure(container);
392: }
393:
394: //call template method, create a new container to allow subclass to override bindings
395: container = container.makeChildContainer();
396: configureBindings(container);
397:
398: return container;
399: }
400:
401: /**
402: * Template method allowing subclass to override any bindings.
403: *
404: * @param container Container containing all bindings, keyed by {@link QName}.
405: */
406: protected void configureBindings(MutablePicoContainer container) {
407: //do nothing
408: }
409:
410: /**
411: * Configures the root context to be used when parsing elements.
412: *
413: * @param container The container representing the context.
414: */
415: public final MutablePicoContainer setupContext(
416: MutablePicoContainer container) {
417: //configure bindings of all dependencies
418: List dependencies = allDependencies();
419: for (Iterator d = dependencies.iterator(); d.hasNext();) {
420: Configuration dependency = (Configuration) d.next();
421:
422: //throw locator and location resolver into context
423: XSDSchemaLocationResolver resolver = dependency
424: .getSchemaLocationResolver();
425: if (resolver != null) {
426: QName key = new QName(dependency.getNamespaceURI(),
427: "schemaLocationResolver");
428: container.registerComponentInstance(key, resolver);
429: }
430: XSDSchemaLocator locator = dependency.getSchemaLocator();
431: if (locator != null) {
432: QName key = new QName(dependency.getNamespaceURI(),
433: "schemaLocator");
434: container.registerComponentInstance(key, locator);
435: }
436:
437: //set any parser properties
438: for (Iterator p = dependency.getProperties().iterator(); p
439: .hasNext();) {
440: QName property = (QName) p.next();
441: try {
442: container.registerComponentInstance(property,
443: property);
444: } catch (DuplicateComponentKeyRegistrationException e) {
445: //ok, ignore
446: }
447: }
448:
449: //add any additional configuration, factories and such
450: // create a new container to allow configurations to override factories in dependant
451: // configurations
452: container = container.makeChildContainer();
453: dependency.configureContext(container);
454: }
455:
456: return container;
457:
458: }
459:
460: /**
461: * Configures the root context to be used when parsing elements.
462: * <p>
463: * The context satisifies any depenencencies needed by a binding. This is
464: * often a factory used to create something.
465: * </p>
466: * <p>
467: * This method should be overriden. The default implementation does nothing.
468: * </p>
469: *
470: * @param container The container representing the context.
471: */
472: protected void configureContext(MutablePicoContainer container) {
473:
474: }
475:
476: /**
477: * Flushes any internal state
478: *
479: * @deprecated This method will be removed in subsequent versions as
480: * configuration class changes are already planned.
481: */
482: public void flush() {
483: schemaLocator = null;
484: }
485:
486: /**
487: * Equals override, equality is based soley on {@link #getNamespaceURI()}.
488: */
489: public final boolean equals(Object obj) {
490: if (obj instanceof Configuration) {
491: Configuration other = (Configuration) obj;
492: return Utilities.equals(getNamespaceURI(), other
493: .getNamespaceURI());
494: }
495:
496: return false;
497: }
498:
499: public final int hashCode() {
500: if (getNamespaceURI() != null) {
501: return getNamespaceURI().hashCode();
502: }
503:
504: return 0;
505: }
506: }
|