001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.validation.impl;
018:
019: import java.io.IOException;
020:
021: import org.apache.avalon.framework.activity.Disposable;
022: import org.apache.avalon.framework.logger.LogEnabled;
023: import org.apache.avalon.framework.logger.Logger;
024: import org.apache.avalon.framework.service.ServiceException;
025: import org.apache.avalon.framework.service.ServiceManager;
026: import org.apache.avalon.framework.service.Serviceable;
027: import org.apache.cocoon.components.validation.Schema;
028: import org.apache.cocoon.components.validation.SchemaParser;
029: import org.apache.cocoon.components.validation.ValidationHandler;
030: import org.apache.cocoon.components.validation.Validator;
031: import org.apache.cocoon.components.validation.ValidatorException;
032: import org.apache.excalibur.source.Source;
033: import org.apache.excalibur.source.SourceResolver;
034: import org.apache.excalibur.xml.sax.NOPContentHandler;
035: import org.apache.excalibur.xml.sax.SAXParser;
036: import org.apache.excalibur.xml.sax.XMLizable;
037: import org.xml.sax.Attributes;
038: import org.xml.sax.ErrorHandler;
039: import org.xml.sax.InputSource;
040: import org.xml.sax.SAXException;
041:
042: /**
043: * <p>The {@link AbstractValidator} provides a generic implementation of the methods
044: * specified by the {@link Validator} interface.</p>
045: *
046: * <p>Final implementations must implement three component management methods
047: * {@link #lookupParserByGrammar(String)}, {@link #lookupParserByName(String)} and
048: * {@link #releaseParser(SchemaParser)}.</p>
049: *
050: * <p>In addition to this, they might also override the default implementation of
051: * the {@link #getSchema(SchemaParser, Source, String)} method, for example when
052: * caching {@link Schema} instances.</p>
053: *
054: * <p>This implementation provides a simple grammar identification mechanism, which
055: * can be overridden by reimplementing the {@link #detectGrammar(Source)} method
056: * provided by this class.</p>
057: *
058: */
059: public abstract class AbstractValidator implements Validator,
060: Serviceable, Disposable, LogEnabled {
061:
062: /** <p>The configured {@link ServiceManager} instance.</p> */
063: protected ServiceManager manager = null;
064: /** <p>The configured {@link SourceResolver} instance.</p> */
065: protected SourceResolver resolver = null;
066: /** <p>The configured {@link Logger} instance.</p> */
067: protected Logger logger = null;
068:
069: /**
070: * <p>Create a new {@link AbstractValidator} instance.</p>
071: */
072: public AbstractValidator() {
073: super ();
074: }
075:
076: /**
077: * <p>Enable logging.</p>
078: */
079: public void enableLogging(Logger logger) {
080: this .logger = logger;
081: }
082:
083: /**
084: * <p>Specify the {@link ServiceManager} available to this instance.</p>
085: */
086: public void service(ServiceManager manager) throws ServiceException {
087: this .manager = manager;
088: this .resolver = (SourceResolver) manager
089: .lookup(SourceResolver.ROLE);
090: }
091:
092: /**
093: * <p>Dispose of this component instance.</p>
094: */
095: public void dispose() {
096: if (this .resolver != null)
097: this .manager.release(this .resolver);
098: }
099:
100: /* =========================================================================== */
101: /* IMPLEMENTATION OF THE VALIDATOR INTERFACE */
102: /* =========================================================================== */
103:
104: /**
105: * <p>Return a {@link ValidationHandler} validating an XML document according to
106: * the schema found at the specified location.</p>
107: *
108: * <p>The {@link Validator} will attempt to automatically detect the grammar
109: * language of the specified schema, and each error or warning occurring while
110: * validating the document will trigger a {@link SAXException} to be thrown back
111: * to the caller.</p>
112: *
113: * @param uri the location of the schema to use to validate the document.
114: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
115: * the original XML document to validate.
116: * @throws IOException if an I/O error occurred parsing the schema.
117: * @throws SAXException if a grammar error occurred parsing the schema.
118: * @throws ValidatorException if the grammar language of the specified schema
119: * could not be detected or was not supported.
120: * @see SchemaParser#parseSchema(Source, String)
121: * @see Schema#createValidator(ErrorHandler)
122: */
123: public ValidationHandler getValidationHandler(String uri)
124: throws IOException, SAXException, ValidatorException {
125: return this .getValidationHandler(uri, null, null);
126: }
127:
128: /**
129: * <p>Return a {@link ValidationHandler} validating an XML document according to
130: * the schema found at the specified location.</p>
131: *
132: * <p>Each error or warning occurring while validating the document will trigger
133: * a {@link SAXException} to be thrown back to the caller.</p>
134: *
135: * @param uri the location of the schema to use to validate the document.
136: * @param grammar the grammar language of the schema to parse.
137: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
138: * the original XML document to validate.
139: * @throws IOException if an I/O error occurred parsing the schema.
140: * @throws SAXException if a grammar error occurred parsing the schema.
141: * @throws ValidatorException if the specified grammar language wasn't supported.
142: * @see SchemaParser#parseSchema(Source, String)
143: * @see Schema#createValidator(ErrorHandler)
144: */
145: public ValidationHandler getValidationHandler(String uri,
146: String grammar) throws IOException, SAXException,
147: ValidatorException {
148: return this .getValidationHandler(uri, grammar, null);
149: }
150:
151: /**
152: * <p>Return a {@link ValidationHandler} validating an XML document according to
153: * the schema found at the specified location.</p>
154: *
155: * <p>The {@link Validator} will attempt to automatically detect the grammar
156: * language of the specified schema, while each validation error or warning will
157: * be passed to the specified {@link ErrorHandler} which will have the ability
158: * to generate and throw a {@link SAXException} back to the caller.</p>
159: *
160: * @param uri the location of the schema to use to validate the document.
161: * @param errorHandler the {@link ErrorHandler} notified of validation problems.
162: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
163: * the original XML document to validate.
164: * @throws IOException if an I/O error occurred parsing the schema.
165: * @throws SAXException if a grammar error occurred parsing the schema.
166: * @throws ValidatorException if the grammar language of the specified schema
167: * could not be detected or was not supported.
168: * @see SchemaParser#parseSchema(Source, String)
169: * @see Schema#createValidator(ErrorHandler)
170: */
171: public ValidationHandler getValidationHandler(String uri,
172: ErrorHandler errorHandler) throws IOException,
173: SAXException, ValidatorException {
174: return this .getValidationHandler(uri, null, errorHandler);
175: }
176:
177: /**
178: * <p>Return a {@link ValidationHandler} validating an XML document according to
179: * the schema found at the specified location.</p>
180: *
181: * <p>Each validation error or warning will be passed to the specified
182: * {@link ErrorHandler} which will have the ability to generate and throw a
183: * {@link SAXException} back to the caller.</p>
184: *
185: * @param uri the location of the schema to use to validate the document.
186: * @param grammar the grammar language of the schema to parse.
187: * @param errorHandler the {@link ErrorHandler} notified of validation problems.
188: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
189: * the original XML document to validate.
190: * @throws IOException if an I/O error occurred parsing the schema.
191: * @throws SAXException if a grammar error occurred parsing the schema.
192: * @throws ValidatorException if the specified grammar language wasn't supported.
193: * @see SchemaParser#parseSchema(Source, String)
194: * @see Schema#createValidator(ErrorHandler)
195: */
196: public ValidationHandler getValidationHandler(String uri,
197: String grammar, ErrorHandler errorHandler)
198: throws IOException, SAXException, ValidatorException {
199: if (uri == null)
200: throw new IOException("Specified schema URI was null");
201: Source source = null;
202: try {
203: source = this .resolver.resolveURI(uri);
204: return this .getValidationHandler(source, grammar,
205: errorHandler);
206: } finally {
207: if (source != null)
208: this .resolver.release(source);
209: }
210: }
211:
212: /**
213: * <p>Return a {@link ValidationHandler} validating an XML document according to
214: * the schema found at the specified location.</p>
215: *
216: * <p>The {@link Validator} will attempt to automatically detect the grammar
217: * language of the specified schema, and each error or warning occurring while
218: * validating the document will trigger a {@link SAXException} to be thrown back
219: * to the caller.</p>
220: *
221: * @param source the {@link Source} identifying the schema to use for validation.
222: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
223: * the original XML document to validate.
224: * @throws IOException if an I/O error occurred parsing the schema.
225: * @throws SAXException if a grammar error occurred parsing the schema.
226: * @throws ValidatorException if the grammar language of the specified schema
227: * could not be detected or was not supported.
228: * @see SchemaParser#parseSchema(Source, String)
229: * @see Schema#createValidator(ErrorHandler)
230: */
231: public ValidationHandler getValidationHandler(Source source)
232: throws IOException, SAXException, ValidatorException {
233: return this .getValidationHandler(source, null, null);
234: }
235:
236: /**
237: * <p>Return a {@link ValidationHandler} validating an XML document according to
238: * the schema found at the specified location.</p>
239: *
240: * <p>Each error or warning occurring while validating the document will trigger
241: * a {@link SAXException} to be thrown back to the caller.</p>
242: *
243: * @param source the {@link Source} identifying the schema to use for validation.
244: * @param grammar the grammar language of the schema to parse.
245: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
246: * the original XML document to validate.
247: * @throws IOException if an I/O error occurred parsing the schema.
248: * @throws SAXException if a grammar error occurred parsing the schema.
249: * @throws ValidatorException if the specified grammar language wasn't supported.
250: * @see SchemaParser#parseSchema(Source, String)
251: * @see Schema#createValidator(ErrorHandler)
252: */
253: public ValidationHandler getValidationHandler(Source source,
254: String grammar) throws IOException, SAXException,
255: ValidatorException {
256: return this .getValidationHandler(source, grammar, null);
257: }
258:
259: /**
260: * <p>Return a {@link ValidationHandler} validating an XML document according to
261: * the schema found at the specified location.</p>
262: *
263: * <p>The {@link Validator} will attempt to automatically detect the grammar
264: * language of the specified schema, while each validation error or warning will
265: * be passed to the specified {@link ErrorHandler} which will have the ability
266: * to generate and throw a {@link SAXException} back to the caller.</p>
267: *
268: * @param source the {@link Source} identifying the schema to use for validation.
269: * @param errorHandler the {@link ErrorHandler} notified of validation problems.
270: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
271: * the original XML document to validate.
272: * @throws IOException if an I/O error occurred parsing the schema.
273: * @throws SAXException if a grammar error occurred parsing the schema.
274: * @throws ValidatorException if the grammar language of the specified schema
275: * could not be detected or was not supported.
276: * @see SchemaParser#parseSchema(Source, String)
277: * @see Schema#createValidator(ErrorHandler)
278: */
279: public ValidationHandler getValidationHandler(Source source,
280: ErrorHandler errorHandler) throws IOException,
281: SAXException, ValidatorException {
282: return this .getValidationHandler(source, null, errorHandler);
283: }
284:
285: /**
286: * <p>Return a {@link ValidationHandler} validating an XML document according to
287: * the schema found at the specified location.</p>
288: *
289: * <p>Each validation error or warning will be passed to the specified
290: * {@link ErrorHandler} which will have the ability to generate and throw a
291: * {@link SAXException} back to the caller.</p>
292: *
293: * @param source the {@link Source} identifying the schema to use for validation.
294: * @param grammar the grammar language of the schema to parse.
295: * @param errorHandler the {@link ErrorHandler} notified of validation problems.
296: * @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
297: * the original XML document to validate.
298: * @throws IOException if an I/O error occurred parsing the schema.
299: * @throws SAXException if a grammar error occurred parsing the schema.
300: * @throws ValidatorException if the specified grammar language wasn't supported.
301: * @see SchemaParser#parseSchema(Source, String)
302: * @see Schema#createValidator(ErrorHandler)
303: */
304: public ValidationHandler getValidationHandler(Source source,
305: String grammar, ErrorHandler errorHandler)
306: throws IOException, SAXException, ValidatorException {
307: if (errorHandler == null)
308: errorHandler = DraconianErrorHandler.INSTANCE;
309:
310: SchemaParser parser = null;
311: try {
312: /* If no grammar was supplied, try to detect one */
313: if (grammar == null)
314: grammar = this .detectGrammar(source);
315:
316: /* Save the grammar name and try to find a schema parser */
317: String language = grammar;
318: parser = this .lookupParserByGrammar(grammar);
319:
320: /*
321: * If the schema parser for the language was not found, we might have to
322: * look up for the form name:grammar as specified by Validator.
323: */
324: if (parser == null) {
325: int index = grammar.indexOf(':');
326: if (index > 0) {
327: String name = grammar.substring(0, index);
328: language = grammar.substring(index + 1);
329: parser = this .lookupParserByName(name);
330: }
331: }
332:
333: /* If still we didn't find any parser, simply die of natural death */
334: if (parser == null) {
335: String message = "Unsupported grammar language"
336: + grammar;
337: throw new ValidatorException(message);
338: }
339:
340: /* Somehow we have a schema parser, check it supports the gramar */
341: String languages[] = parser.getSupportedGrammars();
342: for (int x = 0; x < languages.length; x++) {
343: if (!language.equals(languages[x]))
344: continue;
345: /* Hah! language supported, go ahead and parse now */
346: Schema schema = this
347: .getSchema(parser, source, language);
348: return schema.createValidator(errorHandler);
349: }
350:
351: /* Something really odd going on, this should never happen */
352: String message = "Schema parser "
353: + parser.getClass().getName()
354: + " does not support grammar " + grammar;
355: throw new ValidatorException(message);
356:
357: } finally {
358: if (parser != null)
359: this .releaseParser(parser);
360: }
361: }
362:
363: /* =========================================================================== */
364: /* METHODS TO BE IMPLEMENTED BY THE EXTENDING CLASSES */
365: /* =========================================================================== */
366:
367: /**
368: * <p>Attempt to acquire a {@link SchemaParser} interface able to understand
369: * the grammar language specified.</p>
370: *
371: * @param grammar the grammar language that must be understood by the returned
372: * {@link SchemaParser}
373: * @return a {@link SchemaParser} instance or <b>null</b> if none was found able
374: * to understand the specified grammar language.
375: */
376: protected abstract SchemaParser lookupParserByGrammar(String grammar);
377:
378: /**
379: * <p>Attempt to acquire a {@link SchemaParser} interface associated with the
380: * specified instance name.</p>
381: *
382: * @param name the name associated with the {@link SchemaParser} to be returned.
383: * @return a {@link SchemaParser} instance or <b>null</b> if none was found.
384: */
385: protected abstract SchemaParser lookupParserByName(String name);
386:
387: /**
388: * <p>Release a previously acquired {@link SchemaParser} instance back to its
389: * original component manager.</p>
390: *
391: * <p>This method is supplied in case solid implementations of this class relied
392: * on the {@link ServiceManager} to manage {@link SchemaParser}s instances.</p>
393: *
394: * @param parser the {@link SchemaParser} whose instance is to be released.
395: */
396: protected abstract void releaseParser(SchemaParser parser);
397:
398: /* =========================================================================== */
399: /* METHODS SPECIFIC TO ABSTRACTVALIDATOR OVERRIDABLE BY OTHER IMPLEMENTATIONS */
400: /* =========================================================================== */
401:
402: /**
403: * <p>Return a {@link Schema} instance from the specified {@link SchemaParser}
404: * associated with the given {@link Source} and grammar language.</p>
405: *
406: * <p>This method simply implements resolution returning the {@link Schema}
407: * instance acquired calling <code>parser.getSchema(source,grammar)</code>.</p>
408: *
409: * @param parser the {@link SchemaParser} producing the {@link Schema}.
410: * @param source the {@link Source} associated with the {@link Schema} to return.
411: * @param grammar the grammar language of the schema to produce.
412: * @throws SAXException if a grammar error occurred parsing the schema.
413: * @throws IOException if an I/O error occurred parsing the schema.
414: */
415: protected Schema getSchema(SchemaParser parser, Source source,
416: String grammar) throws IOException, SAXException {
417: if (this .logger.isDebugEnabled()) {
418: this .logger.debug("Parsing schema \"" + source.getURI()
419: + "\" using " + "grammar \"" + grammar
420: + "\" and SourceParser "
421: + parser.getClass().getName());
422: }
423:
424: try {
425: return parser.parseSchema(source, grammar);
426: } catch (IllegalArgumentException exception) {
427: String message = "Schema parser "
428: + parser.getClass().getName()
429: + " does not support grammar " + grammar;
430: throw new ValidatorException(message, exception);
431: }
432: }
433:
434: /**
435: * <p>Attempt to detect the grammar language used by the schema identified
436: * by the specified {@link Source}.</p>
437: *
438: * @param source a {@link Source} instance pointing to the schema to be analyzed.
439: * @throws IOException if an I/O error occurred accessing the schema.
440: * @throws SAXException if an error occurred parsing the schema.
441: * @throws ValidatorException if the language of the schema could not be guessed.
442: */
443: protected String detectGrammar(Source source) throws IOException,
444: SAXException, ValidatorException {
445: if (this .logger.isDebugEnabled()) {
446: this .logger.debug("Detecting grammar for \""
447: + source.getURI() + "\"");
448: }
449:
450: SAXParser xmlParser = null;
451: String grammar = null;
452:
453: try {
454: DetectionHandler handler = new DetectionHandler();
455: if (source instanceof XMLizable) {
456: XMLizable xmlizable = (XMLizable) source;
457: xmlizable.toSAX(handler);
458: } else {
459: xmlParser = (SAXParser) this .manager
460: .lookup((SAXParser.ROLE));
461: InputSource input = new InputSource();
462: input.setSystemId(source.getURI());
463: input.setByteStream(source.getInputStream());
464: xmlParser.parse(input, handler);
465: }
466: } catch (ServiceException exception) {
467: throw new SAXException("Unable to access XML parser",
468: exception);
469: } catch (DetectionException exception) {
470: grammar = exception.grammar;
471: } finally {
472: if (xmlParser != null)
473: this .manager.release(xmlParser);
474: }
475:
476: if (("".equals(grammar)) || (grammar == null)) {
477: String message = "Unable to detect grammar for schema at ";
478: throw new ValidatorException(message + source.getURI());
479: } else {
480: if (this .logger.isDebugEnabled()) {
481: this .logger.debug("Grammar \"" + grammar
482: + "\" detected for " + "schema \""
483: + source.getURI());
484: }
485: return grammar;
486: }
487: }
488:
489: /* =========================================================================== */
490: /* METHODS AND INNER CLASSES FOR AUTOMATIC GRAMMAR LANGUAGE DETECTION */
491: /* =========================================================================== */
492:
493: /**
494: * <p>A simple implementation of a {@link ValidationHandler} detecting schemas
495: * based on the namespace of the root element.</p>
496: */
497: private static final class DetectionHandler extends
498: NOPContentHandler {
499:
500: /**
501: * <p>Detect the namespace of the root element and always throw a
502: * {@link SAXException} or a {@link DetectionException}.</p>
503: */
504: public void startElement(String ns, String lnam, String qnam,
505: Attributes a) throws SAXException {
506: throw new DetectionException(ns);
507: }
508: }
509:
510: /**
511: * <p>An exception thrown by {@link DetectionHandler} representing that
512: * a grammar was successfully detected.</p>
513: */
514: private static final class DetectionException extends SAXException {
515:
516: private final String grammar;
517:
518: private DetectionException(String grammar) {
519: super ("Grammar detected: " + grammar);
520: this.grammar = grammar;
521: }
522: }
523: }
|