blob: c8c95673343d47be89afe2f2d8ba64f8f56b2109 [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.cocoon.components.validation.Schema;
import org.apache.cocoon.components.validation.SchemaParser;
import org.apache.cocoon.components.validation.ValidatorException;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.store.Store;
import org.xml.sax.SAXException;
/**
* <p>An extension of the {@link DefaultValidator} class allowing {@link Schema}
* instances to be cached.</p>
*
* <p>The {@link #getSchema(SchemaParser, Source, String)} method will manage
* whether to return a cached or a freshly parsed {@link Schema} instance.</p>
*
*/
public class CachingValidator extends DefaultValidator {
/** <p>The {@link Store} used for caching {@link Schema}s (if enabled).</p> */
private Store store = null;
/**
* <p>Create a new {@link CachingValidator} instance.</p>
*/
public CachingValidator() {
super();
}
/**
* <p>Initialize this component instance.</p>
*/
public void initialize()
throws Exception {
this.store = (Store) this.manager.lookup(Store.TRANSIENT_STORE);
super.initialize();
}
/**
* <p>Dispose this component instance.</p>
*/
public void dispose() {
try {
super.dispose();
} finally {
if (this.store != null) this.manager.release(this.store);
}
}
/**
* <p>Return a {@link Schema} instance from the specified {@link SchemaParser}
* associated with the given {@link Source} and grammar language.</p>
*
* <p>This method will overriding the default behaviour specified by the
* {@link AbstractValidator#getSchema(SchemaParser, Source, String)} method,
* and supports cacheability of {@link Schema} instances through the use of
* a {@link Store} looked up using the {@link Store#TRANSIENT_STORE} Avalon
* role.</p>
*
* <p>Cached {@link Schema} instances will be retained in the configured
* {@link Store} until the checks on the validity obtained calling the
* {@link Schema#getValidity()} method will declare that the schema is still
* valid.</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.
*/
public Schema getSchema(SchemaParser parser, Source source, String grammar)
throws IOException, SAXException {
/* Prepare a key, and try to get the cached copy of the schema */
String uri = source.getURI();
String key = this.getClass().getName() + "[" + parser.getClass().getName()
+ ":" + grammar + "]@" + source.getURI();
Schema schema = null;
SourceValidity validity = null;
schema = (Schema) this.store.get(key);
/* If the schema was found verify its validity and optionally clear */
if (schema != null) {
validity = schema.getValidity();
if (validity == null) {
/* Why did we cache it in the first place? */
this.logger.warn("Cached schema " + uri + " has null validity");
this.store.remove(key);
schema = null;
} else if (validity.isValid() != SourceValidity.VALID) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Cached schema " + uri + " no longer valid");
}
this.store.remove(key);
schema = null;
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Valid cached schema found for " + uri);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Schema " + uri + " not found in cache");
}
/* If the schema was not cached or was cleared, parse and cache it */
if (schema == null) {
schema = super.getSchema(parser, source, grammar);
validity = schema.getValidity();
if (validity != null) {
if (validity.isValid() == SourceValidity.VALID) {
this.store.store(key, schema);
}
}
}
/* Return the parsed or cached schema */
return schema;
}
/**
* <p>Attempt to detect the grammar language used by the schema identified
* by the specified {@link Source}.</p>
*
* <p>The grammar languages detected will be cached until the {@link Source}'s
* {@link SourceValidity} declares that the schema is valid.</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 {
/* Prepare a key, and try to get the cached copy of the schema */
String uri = source.getURI();
String key = this.getClass().getName() + "@" + source.getURI();
CachedGrammar grammar = null;
grammar = (CachedGrammar) this.store.get(key);
/* If the schema was found verify its validity and optionally clear */
if (grammar != null) {
if (grammar.validity == null) {
/* Why did we cache it in the first place? */
this.logger.warn("Grammar for " + uri + " has null validity");
this.store.remove(key);
grammar = null;
} else if (grammar.validity.isValid() != SourceValidity.VALID) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Grammar for " + uri + " no longer valid");
}
this.store.remove(key);
grammar = null;
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Valid cached grammar " + grammar + " for " + uri);
}
}
/* If the schema was not cached or was cleared, parse and cache it */
if (grammar != null) {
return grammar.grammar;
} else {
String language = super.detectGrammar(source);
SourceValidity validity = source.getValidity();
if (validity != null) {
if (validity.isValid() == SourceValidity.VALID) {
this.store.store(key, new CachedGrammar(validity, language));
}
}
return language;
}
}
/**
* <p>A simple inner class associating grammar languages and source validity
* for caching of schema grammar detection.</p>
*/
private static final class CachedGrammar {
private final SourceValidity validity;
private final String grammar;
private CachedGrammar(SourceValidity validity, String grammar) {
this.validity = validity;
this.grammar = grammar;
}
}
}