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