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.impl;
017:
018: import java.util.ArrayList;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Stack;
023: import java.util.logging.Level;
024: import java.util.logging.Logger;
025:
026: import javax.xml.namespace.QName;
027:
028: import org.eclipse.xsd.XSDElementDeclaration;
029: import org.eclipse.xsd.XSDFactory;
030: import org.eclipse.xsd.XSDImport;
031: import org.eclipse.xsd.XSDParticle;
032: import org.eclipse.xsd.XSDSchema;
033: import org.eclipse.xsd.XSDSimpleTypeDefinition;
034: import org.eclipse.xsd.XSDTypeDefinition;
035: import org.eclipse.xsd.util.XSDSchemaLocationResolver;
036: import org.eclipse.xsd.util.XSDSchemaLocator;
037: import org.eclipse.xsd.util.XSDUtil;
038: import org.geotools.xml.BindingFactory;
039: import org.geotools.xml.Configuration;
040: import org.geotools.xml.ElementInstance;
041: import org.geotools.xml.Parser;
042: import org.geotools.xml.SchemaIndex;
043: import org.geotools.xml.Schemas;
044: import org.geotools.xs.bindings.XS;
045: import org.picocontainer.MutablePicoContainer;
046: import org.picocontainer.defaults.DefaultPicoContainer;
047: import org.xml.sax.Attributes;
048: import org.xml.sax.SAXException;
049: import org.xml.sax.SAXParseException;
050: import org.xml.sax.helpers.DefaultHandler;
051: import org.xml.sax.helpers.NamespaceSupport;
052:
053: /**
054: *
055: * The main sax event handler used for parsing the input document. This handler
056: * maintains a stack of {@link Handler} objects. A handler is purshed onto the stack
057: * when a startElement event is processed, and popped off the stack when the corresponding
058: * endElement event is processed.
059: *
060: * @author Justin Deoliveira,Refractions Research Inc.,jdeolive@refractions.net
061: *
062: */
063: public class ParserHandler extends DefaultHandler {
064: /** execution stack **/
065: protected Stack handlers;
066:
067: /** namespace support **/
068: NamespaceSupport namespaces;
069:
070: /** imported schemas **/
071: XSDSchema[] schemas;
072:
073: /** index used to look up schema elements **/
074: SchemaIndex index;
075:
076: /** handler factory **/
077: HandlerFactory handlerFactory;
078:
079: /** binding loader */
080: BindingLoader bindingLoader;
081:
082: /** bindign walker */
083: BindingWalker bindingWalker;
084:
085: /**
086: * binding factory
087: */
088: BindingFactory bindingFactory;
089:
090: /** the document handler **/
091: DocumentHandler documentHandler;
092:
093: /** parser config **/
094: Configuration config;
095:
096: /** context, container **/
097: MutablePicoContainer context;
098:
099: /** logger **/
100: Logger logger;
101:
102: /** flag to indicate if the parser should validate or not */
103: boolean validating;
104:
105: /** wether the parser is strict or not */
106: boolean strict = false;
107:
108: /** list of "errors" that occur while parsing */
109: List errors;
110:
111: public ParserHandler(Configuration config) {
112: this .config = config;
113: errors = new ArrayList();
114: namespaces = new NamespaceSupport();
115: validating = false;
116: }
117:
118: public Configuration getConfiguration() {
119: return config;
120: }
121:
122: public void setStrict(boolean strict) {
123: this .strict = strict;
124: }
125:
126: public boolean isStrict() {
127: return strict;
128: }
129:
130: public boolean isValidating() {
131: return validating;
132: }
133:
134: public void setValidating(boolean validating) {
135: this .validating = validating;
136: }
137:
138: public List getValidationErrors() {
139: return errors;
140: }
141:
142: public HandlerFactory getHandlerFactory() {
143: return handlerFactory;
144: }
145:
146: public BindingLoader getBindingLoader() {
147: return bindingLoader;
148: }
149:
150: public BindingWalker getBindingWalker() {
151: return bindingWalker;
152: }
153:
154: public BindingFactory getBindingFactory() {
155: return bindingFactory;
156: }
157:
158: public XSDSchema[] getSchemas() {
159: return schemas;
160: }
161:
162: public SchemaIndex getSchemaIndex() {
163: return index;
164: }
165:
166: public Logger getLogger() {
167: return logger;
168: }
169:
170: public NamespaceSupport getNamespaceSupport() {
171: return namespaces;
172: }
173:
174: public void startPrefixMapping(String prefix, String uri)
175: throws SAXException {
176: namespaces.declarePrefix(prefix, uri);
177: }
178:
179: public void startDocument() throws SAXException {
180: //perform teh configuration
181: configure(config);
182:
183: //create the document handler + root context
184: DocumentHandler docHandler = handlerFactory
185: .createDocumentHandler(this );
186:
187: context = new DefaultPicoContainer();
188: context = config.setupContext(context);
189:
190: docHandler.setContext(context);
191:
192: // create the stack and add handler for document element
193: handlers = new Stack();
194: handlers.push(docHandler);
195:
196: // get a logger from the context
197: logger = (Logger) context
198: .getComponentInstanceOfType(Logger.class);
199:
200: if (logger == null) {
201: //create a default
202: logger = org.geotools.util.logging.Logging
203: .getLogger("org.geotools.xml");
204: context.registerComponentInstance(logger);
205: }
206:
207: //setup the namespace support
208: context.registerComponentInstance(namespaces);
209: context.registerComponentInstance(new NamespaceSupportWrapper(
210: namespaces));
211:
212: //binding factory support
213: bindingFactory = new BindingFactoryImpl(bindingLoader);
214: context.registerComponentInstance(bindingFactory);
215:
216: //binding walker support
217: context.registerComponentInstance(new BindingWalkerFactoryImpl(
218: bindingLoader, context));
219: }
220:
221: public void startElement(String uri, String localName,
222: String qName, Attributes attributes) throws SAXException {
223: logger.finest("startElement(" + uri + "," + localName + ","
224: + qName);
225: if (schemas == null) {
226: //root element, parse the schema
227: //TODO: this processing is too loose, do some validation will ya!
228: String[] locations = null;
229:
230: // if ( context.getComponentInstance( Parser.Properties.IGNORE_SCHEMA_LOCATION ) != null ) {
231: // //use the configuration
232: // locations = new String[] {
233: // config.getNamespaceURI(), config.getSchemaFileURL()
234: // };
235: // }
236: // else {
237: for (int i = 0; i < attributes.getLength(); i++) {
238: String name = attributes.getQName(i);
239:
240: if (name.endsWith("schemaLocation")) {
241: //create an array of alternating namespace, location pairs
242: locations = attributes.getValue(i).split(" +");
243:
244: break;
245: }
246: }
247: // }
248:
249: if (!isStrict() && locations == null) {
250: //use the configuration
251: locations = new String[] { config.getNamespaceURI(),
252: config.getSchemaFileURL() };
253: }
254:
255: //look up schema overrides
256: XSDSchemaLocator[] locators = findSchemaLocators();
257: XSDSchemaLocationResolver[] resolvers = findSchemaLocationResolvers();
258:
259: if ((locations != null) && (locations.length > 0)) {
260: //parse each namespace location pair into schema objects
261: schemas = new XSDSchema[locations.length / 2];
262:
263: for (int i = 0; i < locations.length; i += 2) {
264: String namespace = locations[i];
265: String location = locations[i + 1];
266:
267: //first check for a location override
268: for (int j = 0; j < resolvers.length; j++) {
269: String override = resolvers[j]
270: .resolveSchemaLocation(null, namespace,
271: location);
272:
273: if (override != null) {
274: location = override;
275:
276: break;
277: }
278: }
279:
280: //next check for schema override
281: for (int j = 0; j < locators.length; j++) {
282: XSDSchema schema = locators[j].locateSchema(
283: null, namespace, location, null);
284:
285: if (schema != null) {
286: schemas[i / 2] = schema;
287:
288: break;
289: }
290: }
291:
292: //if no schema override was found, parse location directly
293: if (schemas[i / 2] == null) {
294: try {
295: schemas[i / 2] = Schemas.parse(location,
296: locators, resolvers);
297: } catch (Exception e) {
298: String msg = "Error parsing: " + location;
299: logger.warning(msg);
300: throw (SAXException) new SAXException(msg)
301: .initCause(e);
302: }
303: }
304: }
305: } else {
306: //could not find a schemaLocation attribute, use the locators
307: //look for schmea with locators
308: for (int i = 0; i < locators.length; i++) {
309: XSDSchema schema = locators[i].locateSchema(null,
310: uri, null, null);
311:
312: if (schema != null) {
313: schemas = new XSDSchema[] { schema };
314:
315: break;
316: }
317: }
318: }
319:
320: if (schemas == null) {
321: //crap out
322: String msg = "Could not find a schemaLocation attribute or "
323: + "appropriate locator";
324: throw new SAXException(msg);
325: }
326:
327: //check to make sure that the schemas that were created include
328: // the schema for the parser configuration
329: boolean found = false;
330: O: for (int i = 0; i < schemas.length; i++) {
331: List imports = Schemas.getImports(schemas[i]);
332: for (Iterator im = imports.iterator(); im.hasNext();) {
333: XSDImport imprt = (XSDImport) im.next();
334: if (config.getNamespaceURI().equals(
335: imprt.getNamespace())) {
336: found = true;
337: break O;
338: }
339: }
340: }
341: if (!found) {
342: //add it if not operating in strict mode
343: if (!isStrict()) {
344: logger
345: .fine("schema specified by parser configuration not found, supplementing...");
346:
347: XSDSchema[] copy = new XSDSchema[schemas.length + 1];
348: System.arraycopy(schemas, 0, copy, 0,
349: schemas.length);
350: copy[schemas.length] = config.schema();
351: schemas = copy;
352: } else {
353: String msg = "parser configuration specified schema: '"
354: + config.getNamespaceURI()
355: + "', but instance document does not reference this schema.";
356: logger.info(msg);
357: }
358: }
359:
360: index = new SchemaIndexImpl(schemas);
361:
362: //if no default prefix is set in this namespace context, then
363: // set it to be the namesapce of the configuration
364: if (namespaces.getURI("") == null) {
365: namespaces.declarePrefix("", config.getNamespaceURI());
366: }
367: }
368:
369: //set up a new namespace context
370: namespaces.pushContext();
371:
372: //create a qName object from the string
373: if (uri == null || uri.equals("")) {
374: uri = namespaces.getURI("");
375: }
376: QName qualifiedName = new QName(uri, localName);
377:
378: //get the handler at top of the stack and lookup child
379:
380: //First ask teh parent handler for a child
381: Handler parent = (Handler) handlers.peek();
382: ElementHandler handler = (ElementHandler) parent
383: .createChildHandler(qualifiedName);
384:
385: if (handler == null) {
386: //look for a global element
387: XSDElementDeclaration element = index
388: .getElementDeclaration(qualifiedName);
389: if (element != null) {
390: handler = handlerFactory.createElementHandler(element,
391: parent, this );
392: }
393:
394: }
395:
396: if (handler == null) {
397: //perform a lookup in the context for an element factory that create a child handler
398: List handlerFactories = context
399: .getComponentInstancesOfType(HandlerFactory.class);
400: for (Iterator hf = handlerFactories.iterator(); handler == null
401: && hf.hasNext();) {
402: HandlerFactory handlerFactory = (HandlerFactory) hf
403: .next();
404: handler = handlerFactory.createElementHandler(
405: qualifiedName, parent, this );
406: }
407: }
408:
409: if (handler == null) {
410: //if the type only contains one type of element, just assume the
411: // the element is of that type
412: //if( context.getComponentInstance( Parser.Properties.PARSE_UNKNOWN_ELEMENTS ) != null) {
413: if (!isStrict()) {
414: String msg = "Could not find declaration for: "
415: + qualifiedName
416: + ". Checking if containing type declares a single particle.";
417: logger.fine(msg);
418:
419: if (parent.getComponent() instanceof ElementInstance) {
420: ElementInstance parentElement = (ElementInstance) parent
421: .getComponent();
422: List childParticles = index
423: .getChildElementParticles(parentElement
424: .getElementDeclaration());
425: if (childParticles.size() == 1) {
426: XSDParticle particle = (XSDParticle) childParticles
427: .iterator().next();
428: XSDElementDeclaration child = (XSDElementDeclaration) particle
429: .getContent();
430: if (child.isElementDeclarationReference()) {
431: child = child
432: .getResolvedElementDeclaration();
433: }
434:
435: handler = handlerFactory.createElementHandler(
436: new QName(child.getTargetNamespace(),
437: child.getName()), parent, this );
438: }
439: }
440: }
441: }
442:
443: if (handler == null) {
444: //check the case of where the namespace is wrong, do a lookup from
445: // the parent just on local name
446: if (!isStrict()) {
447: String msg = "Could not find declaration for: "
448: + qualifiedName
449: + ". Performing lookup by ignoring namespace";
450: logger.fine(msg);
451:
452: // * = match any namespace
453: handler = (ElementHandler) parent
454: .createChildHandler(new QName("*",
455: qualifiedName.getLocalPart()));
456:
457: }
458: }
459:
460: if (handler == null) {
461: //check the parser flag, and just parse it anyways
462: //if( context.getComponentInstance( Parser.Properties.PARSE_UNKNOWN_ELEMENTS ) != null) {
463: if (!isStrict()) {
464: String msg = "Could not find declaration for: "
465: + qualifiedName
466: + ". Creating a mock element declaration and parsing anyways...";
467: logger.fine(msg);
468:
469: //create a mock element declaration
470: XSDElementDeclaration decl = XSDFactory.eINSTANCE
471: .createXSDElementDeclaration();
472: decl.setName(qualifiedName.getLocalPart());
473: decl
474: .setTargetNamespace(qualifiedName
475: .getNamespaceURI());
476:
477: //set the type to be of string
478: XSDTypeDefinition type = index
479: .getTypeDefinition(XS.ANYTYPE);
480: decl.setTypeDefinition(type);
481: handler = new ElementHandlerImpl(decl, parent, this );
482: }
483: }
484:
485: if (handler != null) {
486:
487: //we may have actually matched an element whose namespace does
488: // not match the one passed in, update the context if so
489: if (handler.getElementDeclaration().getTargetNamespace() != null
490: && !handler.getElementDeclaration()
491: .getTargetNamespace().equals(uri)) {
492:
493: //only do this for non abstract elements
494: if (!handler.getElementDeclaration().isAbstract()) {
495: namespaces.declarePrefix("", handler
496: .getElementDeclaration()
497: .getTargetNamespace());
498: qualifiedName = new QName(handler
499: .getElementDeclaration()
500: .getTargetNamespace(), qualifiedName
501: .getLocalPart());
502: }
503: }
504:
505: //signal the handler to start the element, and place it on the stack
506: handler.startElement(qualifiedName, attributes);
507: handlers.push(handler);
508: } else {
509: String msg = "Handler for " + qName
510: + " could not be found.";
511: throw new SAXException(msg);
512: }
513: }
514:
515: public void characters(char[] ch, int start, int length)
516: throws SAXException {
517: //pull the handler from the top of stack
518: ElementHandler handler = (ElementHandler) handlers.peek();
519: handler.characters(ch, start, length);
520: }
521:
522: public void endElement(String uri, String localName, String qName)
523: throws SAXException {
524: //pop the last handler off of the stack
525: ElementHandler handler = (ElementHandler) handlers.pop();
526:
527: //create a qName object from the string
528: QName qualifiedName = new QName(uri, localName);
529:
530: handler.endElement(qualifiedName);
531:
532: endElementInternal(handler);
533:
534: //pop namespace context
535: namespaces.popContext();
536: }
537:
538: protected void endElementInternal(ElementHandler handler) {
539: //do nothing
540: }
541:
542: public void endDocument() throws SAXException {
543: //only the document handler should be left on the stack
544: documentHandler = (DocumentHandler) handlers.pop();
545:
546: synchronized (this ) {
547: notifyAll();
548: }
549: }
550:
551: public void warning(SAXParseException e) throws SAXException {
552: //errors.add( e );
553: }
554:
555: public void error(SAXParseException e) throws SAXException {
556: logger.log(Level.WARNING, e.getMessage());
557: errors.add(e);
558: }
559:
560: public Object getValue() {
561: return documentHandler.getParseNode().getValue();
562: }
563:
564: protected void configure(Configuration config) {
565: handlerFactory = new HandlerFactoryImpl();
566: bindingLoader = new BindingLoader();
567: bindingWalker = new BindingWalker(bindingLoader);
568:
569: //configure the bindings
570: MutablePicoContainer container = bindingLoader.getContainer();
571: container = config.setupBindings(container);
572: bindingLoader.setContainer(container);
573: }
574:
575: protected XSDSchemaLocator[] findSchemaLocators() {
576: List l = Schemas.getComponentInstancesOfType(context,
577: XSDSchemaLocator.class);
578: //List l = context.getComponentInstancesOfType(XSDSchemaLocator.class);
579:
580: if ((l == null) || l.isEmpty()) {
581: return new XSDSchemaLocator[] {};
582: }
583:
584: return (XSDSchemaLocator[]) l.toArray(new XSDSchemaLocator[l
585: .size()]);
586: }
587:
588: protected XSDSchemaLocationResolver[] findSchemaLocationResolvers() {
589: //List l = context.getComponentInstancesOfType(XSDSchemaLocationResolver.class);
590: List l = Schemas.getComponentInstancesOfType(context,
591: XSDSchemaLocationResolver.class);
592:
593: if ((l == null) || l.isEmpty()) {
594: return new XSDSchemaLocationResolver[] {};
595: }
596:
597: return (XSDSchemaLocationResolver[]) l
598: .toArray(new XSDSchemaLocationResolver[l.size()]);
599: }
600: }
|