/* * 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; } } }