blob: d32b53c61d75c022d3f4d92993755c3f2f3403a7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.components.validation.impl;
import java.io.IOException;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.logger.LogEnabled;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.components.validation.Schema;
import org.apache.cocoon.components.validation.SchemaParser;
import org.apache.cocoon.components.validation.ValidationHandler;
import org.apache.cocoon.components.validation.Validator;
import org.apache.cocoon.components.validation.ValidatorException;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceResolver;
import org.apache.excalibur.xml.sax.NOPContentHandler;
import org.apache.excalibur.xml.sax.SAXParser;
import org.apache.excalibur.xml.sax.XMLizable;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* <p>The {@link AbstractValidator} provides a generic implementation of the methods
* specified by the {@link Validator} interface.</p>
*
* <p>Final implementations must implement three component management methods
* {@link #lookupParserByGrammar(String)}, {@link #lookupParserByName(String)} and
* {@link #releaseParser(SchemaParser)}.</p>
*
* <p>In addition to this, they might also override the default implementation of
* the {@link #getSchema(SchemaParser, Source, String)} method, for example when
* caching {@link Schema} instances.</p>
*
* <p>This implementation provides a simple grammar identification mechanism, which
* can be overridden by reimplementing the {@link #detectGrammar(Source)} method
* provided by this class.</p>
*
*/
public abstract class AbstractValidator
implements Validator, Serviceable, Disposable, LogEnabled {
/** <p>The configured {@link ServiceManager} instance.</p> */
protected ServiceManager manager = null;
/** <p>The configured {@link SourceResolver} instance.</p> */
protected SourceResolver resolver = null;
/** <p>The configured {@link Logger} instance.</p> */
protected Logger logger = null;
/**
* <p>Create a new {@link AbstractValidator} instance.</p>
*/
public AbstractValidator() {
super();
}
/**
* <p>Enable logging.</p>
*/
public void enableLogging(Logger logger) {
this.logger = logger;
}
/**
* <p>Specify the {@link ServiceManager} available to this instance.</p>
*/
public void service(ServiceManager manager)
throws ServiceException {
this.manager = manager;
this.resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
}
/**
* <p>Dispose of this component instance.</p>
*/
public void dispose() {
if (this.resolver != null) this.manager.release(this.resolver);
}
/* =========================================================================== */
/* IMPLEMENTATION OF THE VALIDATOR INTERFACE */
/* =========================================================================== */
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>The {@link Validator} will attempt to automatically detect the grammar
* language of the specified schema, and each error or warning occurring while
* validating the document will trigger a {@link SAXException} to be thrown back
* to the caller.</p>
*
* @param uri the location of the schema to use to validate the document.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the grammar language of the specified schema
* could not be detected or was not supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(String uri)
throws IOException, SAXException, ValidatorException {
return this.getValidationHandler(uri, null, null);
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>Each error or warning occurring while validating the document will trigger
* a {@link SAXException} to be thrown back to the caller.</p>
*
* @param uri the location of the schema to use to validate the document.
* @param grammar the grammar language of the schema to parse.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the specified grammar language wasn't supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(String uri, String grammar)
throws IOException, SAXException, ValidatorException {
return this.getValidationHandler(uri, grammar, null);
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>The {@link Validator} will attempt to automatically detect the grammar
* language of the specified schema, while each validation error or warning will
* be passed to the specified {@link ErrorHandler} which will have the ability
* to generate and throw a {@link SAXException} back to the caller.</p>
*
* @param uri the location of the schema to use to validate the document.
* @param errorHandler the {@link ErrorHandler} notified of validation problems.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the grammar language of the specified schema
* could not be detected or was not supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(String uri, ErrorHandler errorHandler)
throws IOException, SAXException, ValidatorException {
return this.getValidationHandler(uri, null, errorHandler);
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>Each validation error or warning will be passed to the specified
* {@link ErrorHandler} which will have the ability to generate and throw a
* {@link SAXException} back to the caller.</p>
*
* @param uri the location of the schema to use to validate the document.
* @param grammar the grammar language of the schema to parse.
* @param errorHandler the {@link ErrorHandler} notified of validation problems.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the specified grammar language wasn't supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(String uri, String grammar,
ErrorHandler errorHandler)
throws IOException, SAXException, ValidatorException {
if (uri == null) throw new IOException("Specified schema URI was null");
Source source = null;
try {
source = this.resolver.resolveURI(uri);
return this.getValidationHandler(source, grammar, errorHandler);
} finally {
if (source != null) this.resolver.release(source);
}
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>The {@link Validator} will attempt to automatically detect the grammar
* language of the specified schema, and each error or warning occurring while
* validating the document will trigger a {@link SAXException} to be thrown back
* to the caller.</p>
*
* @param source the {@link Source} identifying the schema to use for validation.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the grammar language of the specified schema
* could not be detected or was not supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(Source source)
throws IOException, SAXException, ValidatorException {
return this.getValidationHandler(source, null, null);
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>Each error or warning occurring while validating the document will trigger
* a {@link SAXException} to be thrown back to the caller.</p>
*
* @param source the {@link Source} identifying the schema to use for validation.
* @param grammar the grammar language of the schema to parse.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the specified grammar language wasn't supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(Source source, String grammar)
throws IOException, SAXException, ValidatorException {
return this.getValidationHandler(source, grammar, null);
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>The {@link Validator} will attempt to automatically detect the grammar
* language of the specified schema, while each validation error or warning will
* be passed to the specified {@link ErrorHandler} which will have the ability
* to generate and throw a {@link SAXException} back to the caller.</p>
*
* @param source the {@link Source} identifying the schema to use for validation.
* @param errorHandler the {@link ErrorHandler} notified of validation problems.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the grammar language of the specified schema
* could not be detected or was not supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(Source source,
ErrorHandler errorHandler)
throws IOException, SAXException, ValidatorException {
return this.getValidationHandler(source, null, errorHandler);
}
/**
* <p>Return a {@link ValidationHandler} validating an XML document according to
* the schema found at the specified location.</p>
*
* <p>Each validation error or warning will be passed to the specified
* {@link ErrorHandler} which will have the ability to generate and throw a
* {@link SAXException} back to the caller.</p>
*
* @param source the {@link Source} identifying the schema to use for validation.
* @param grammar the grammar language of the schema to parse.
* @param errorHandler the {@link ErrorHandler} notified of validation problems.
* @return a <b>non null</b> {@link ValidationHandler} able to SAX events from
* the original XML document to validate.
* @throws IOException if an I/O error occurred parsing the schema.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws ValidatorException if the specified grammar language wasn't supported.
* @see SchemaParser#parseSchema(Source, String)
* @see Schema#createValidator(ErrorHandler)
*/
public ValidationHandler getValidationHandler(Source source, String grammar,
ErrorHandler errorHandler)
throws IOException, SAXException, ValidatorException {
if (errorHandler == null) errorHandler = DraconianErrorHandler.INSTANCE;
SchemaParser parser = null;
try {
/* If no grammar was supplied, try to detect one */
if (grammar == null) grammar = this.detectGrammar(source);
/* Save the grammar name and try to find a schema parser */
String language = grammar;
parser = this.lookupParserByGrammar(grammar);
/*
* If the schema parser for the language was not found, we might have to
* look up for the form name:grammar as specified by Validator.
*/
if (parser == null) {
int index = grammar.indexOf(':');
if (index > 0) {
String name = grammar.substring(0, index);
language = grammar.substring(index + 1);
parser = this.lookupParserByName(name);
}
}
/* If still we didn't find any parser, simply die of natural death */
if (parser == null) {
String message = "Unsupported grammar language" + grammar;
throw new ValidatorException(message);
}
/* Somehow we have a schema parser, check it supports the gramar */
String languages[] = parser.getSupportedGrammars();
for (int x = 0; x < languages.length; x++) {
if (! language.equals(languages[x])) continue;
/* Hah! language supported, go ahead and parse now */
Schema schema = this.getSchema(parser, source, language);
return schema.createValidator(errorHandler);
}
/* Something really odd going on, this should never happen */
String message = "Schema parser " + parser.getClass().getName() +
" does not support grammar " + grammar;
throw new ValidatorException(message);
} finally {
if (parser != null) this.releaseParser(parser);
}
}
/* =========================================================================== */
/* METHODS TO BE IMPLEMENTED BY THE EXTENDING CLASSES */
/* =========================================================================== */
/**
* <p>Attempt to acquire a {@link SchemaParser} interface able to understand
* the grammar language specified.</p>
*
* @param grammar the grammar language that must be understood by the returned
* {@link SchemaParser}
* @return a {@link SchemaParser} instance or <b>null</b> if none was found able
* to understand the specified grammar language.
*/
protected abstract SchemaParser lookupParserByGrammar(String grammar);
/**
* <p>Attempt to acquire a {@link SchemaParser} interface associated with the
* specified instance name.</p>
*
* @param name the name associated with the {@link SchemaParser} to be returned.
* @return a {@link SchemaParser} instance or <b>null</b> if none was found.
*/
protected abstract SchemaParser lookupParserByName(String name);
/**
* <p>Release a previously acquired {@link SchemaParser} instance back to its
* original component manager.</p>
*
* <p>This method is supplied in case solid implementations of this class relied
* on the {@link ServiceManager} to manage {@link SchemaParser}s instances.</p>
*
* @param parser the {@link SchemaParser} whose instance is to be released.
*/
protected abstract void releaseParser(SchemaParser parser);
/* =========================================================================== */
/* METHODS SPECIFIC TO ABSTRACTVALIDATOR OVERRIDABLE BY OTHER IMPLEMENTATIONS */
/* =========================================================================== */
/**
* <p>Return a {@link Schema} instance from the specified {@link SchemaParser}
* associated with the given {@link Source} and grammar language.</p>
*
* <p>This method simply implements resolution returning the {@link Schema}
* instance acquired calling <code>parser.getSchema(source,grammar)</code>.</p>
*
* @param parser the {@link SchemaParser} producing the {@link Schema}.
* @param source the {@link Source} associated with the {@link Schema} to return.
* @param grammar the grammar language of the schema to produce.
* @throws SAXException if a grammar error occurred parsing the schema.
* @throws IOException if an I/O error occurred parsing the schema.
*/
protected Schema getSchema(SchemaParser parser, Source source, String grammar)
throws IOException, SAXException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsing schema \"" + source.getURI() + "\" using " +
"grammar \"" + grammar + "\" and SourceParser " +
parser.getClass().getName());
}
try {
return parser.parseSchema(source, grammar);
} catch (IllegalArgumentException exception) {
String message = "Schema parser " + parser.getClass().getName() +
" does not support grammar " + grammar;
throw new ValidatorException(message, exception);
}
}
/**
* <p>Attempt to detect the grammar language used by the schema identified
* by the specified {@link Source}.</p>
*
* @param source a {@link Source} instance pointing to the schema to be analyzed.
* @throws IOException if an I/O error occurred accessing the schema.
* @throws SAXException if an error occurred parsing the schema.
* @throws ValidatorException if the language of the schema could not be guessed.
*/
protected String detectGrammar(Source source)
throws IOException, SAXException, ValidatorException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Detecting grammar for \"" + source.getURI() + "\"");
}
SAXParser xmlParser = null;
String grammar = null;
try {
DetectionHandler handler = new DetectionHandler();
if (source instanceof XMLizable) {
XMLizable xmlizable = (XMLizable) source;
xmlizable.toSAX(handler);
} else {
xmlParser = (SAXParser) this.manager.lookup((SAXParser.ROLE));
InputSource input = new InputSource();
input.setSystemId(source.getURI());
input.setByteStream(source.getInputStream());
xmlParser.parse(input, handler);
}
} catch (ServiceException exception) {
throw new SAXException("Unable to access XML parser", exception);
} catch (DetectionException exception) {
grammar = exception.grammar;
} finally {
if (xmlParser != null) this.manager.release(xmlParser);
}
if (("".equals(grammar)) || (grammar == null)) {
String message = "Unable to detect grammar for schema at ";
throw new ValidatorException(message + source.getURI());
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Grammar \"" + grammar + "\" detected for " +
"schema \"" + source.getURI());
}
return grammar;
}
}
/* =========================================================================== */
/* METHODS AND INNER CLASSES FOR AUTOMATIC GRAMMAR LANGUAGE DETECTION */
/* =========================================================================== */
/**
* <p>A simple implementation of a {@link ValidationHandler} detecting schemas
* based on the namespace of the root element.</p>
*/
private static final class DetectionHandler extends NOPContentHandler {
/**
* <p>Detect the namespace of the root element and always throw a
* {@link SAXException} or a {@link DetectionException}.</p>
*/
public void startElement(String ns, String lnam, String qnam, Attributes a)
throws SAXException {
throw new DetectionException(ns);
}
}
/**
* <p>An exception thrown by {@link DetectionHandler} representing that
* a grammar was successfully detected.</p>
*/
private static final class DetectionException extends SAXException {
private final String grammar;
private DetectionException(String grammar) {
super ("Grammar detected: " + grammar);
this.grammar = grammar;
}
}
}