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.cocoon.components.validation.Schema;
022: import org.apache.cocoon.components.validation.SchemaParser;
023: import org.apache.cocoon.components.validation.ValidatorException;
024: import org.apache.excalibur.source.Source;
025: import org.apache.excalibur.source.SourceValidity;
026: import org.apache.excalibur.store.Store;
027: import org.xml.sax.SAXException;
028:
029: /**
030: * <p>An extension of the {@link DefaultValidator} class allowing {@link Schema}
031: * instances to be cached.</p>
032: *
033: * <p>The {@link #getSchema(SchemaParser, Source, String)} method will manage
034: * whether to return a cached or a freshly parsed {@link Schema} instance.</p>
035: *
036: */
037: public class CachingValidator extends DefaultValidator {
038:
039: /** <p>The {@link Store} used for caching {@link Schema}s (if enabled).</p> */
040: private Store store = null;
041:
042: /**
043: * <p>Create a new {@link CachingValidator} instance.</p>
044: */
045: public CachingValidator() {
046: super ();
047: }
048:
049: /**
050: * <p>Initialize this component instance.</p>
051: */
052: public void initialize() throws Exception {
053: this .store = (Store) this .manager.lookup(Store.TRANSIENT_STORE);
054: super .initialize();
055: }
056:
057: /**
058: * <p>Dispose this component instance.</p>
059: */
060: public void dispose() {
061: try {
062: super .dispose();
063: } finally {
064: if (this .store != null)
065: this .manager.release(this .store);
066: }
067: }
068:
069: /**
070: * <p>Return a {@link Schema} instance from the specified {@link SchemaParser}
071: * associated with the given {@link Source} and grammar language.</p>
072: *
073: * <p>This method will overriding the default behaviour specified by the
074: * {@link AbstractValidator#getSchema(SchemaParser, Source, String)} method,
075: * and supports cacheability of {@link Schema} instances through the use of
076: * a {@link Store} looked up using the {@link Store#TRANSIENT_STORE} Avalon
077: * role.</p>
078: *
079: * <p>Cached {@link Schema} instances will be retained in the configured
080: * {@link Store} until the checks on the validity obtained calling the
081: * {@link Schema#getValidity()} method will declare that the schema is still
082: * valid.</p>
083: *
084: * @param parser the {@link SchemaParser} producing the {@link Schema}.
085: * @param source the {@link Source} associated with the {@link Schema} to return.
086: * @param grammar the grammar language of the schema to produce.
087: * @throws SAXException if a grammar error occurred parsing the schema.
088: * @throws IOException if an I/O error occurred parsing the schema.
089: */
090: public Schema getSchema(SchemaParser parser, Source source,
091: String grammar) throws IOException, SAXException {
092:
093: /* Prepare a key, and try to get the cached copy of the schema */
094: String uri = source.getURI();
095: String key = this .getClass().getName() + "["
096: + parser.getClass().getName() + ":" + grammar + "]@"
097: + source.getURI();
098: Schema schema = null;
099: SourceValidity validity = null;
100: schema = (Schema) this .store.get(key);
101:
102: /* If the schema was found verify its validity and optionally clear */
103: if (schema != null) {
104: validity = schema.getValidity();
105: if (validity == null) {
106: /* Why did we cache it in the first place? */
107: this .logger.warn("Cached schema " + uri
108: + " has null validity");
109: this .store.remove(key);
110: schema = null;
111: } else if (validity.isValid() != SourceValidity.VALID) {
112: if (this .logger.isDebugEnabled()) {
113: this .logger.debug("Cached schema " + uri
114: + " no longer valid");
115: }
116: this .store.remove(key);
117: schema = null;
118: } else if (this .logger.isDebugEnabled()) {
119: this .logger.debug("Valid cached schema found for "
120: + uri);
121: }
122: } else if (this .logger.isDebugEnabled()) {
123: this .logger.debug("Schema " + uri + " not found in cache");
124: }
125:
126: /* If the schema was not cached or was cleared, parse and cache it */
127: if (schema == null) {
128: schema = super .getSchema(parser, source, grammar);
129: validity = schema.getValidity();
130: if (validity != null) {
131: if (validity.isValid() == SourceValidity.VALID) {
132: this .store.store(key, schema);
133: }
134: }
135: }
136:
137: /* Return the parsed or cached schema */
138: return schema;
139: }
140:
141: /**
142: * <p>Attempt to detect the grammar language used by the schema identified
143: * by the specified {@link Source}.</p>
144: *
145: * <p>The grammar languages detected will be cached until the {@link Source}'s
146: * {@link SourceValidity} declares that the schema is valid.</p>
147: *
148: * @param source a {@link Source} instance pointing to the schema to be analyzed.
149: * @throws IOException if an I/O error occurred accessing the schema.
150: * @throws SAXException if an error occurred parsing the schema.
151: * @throws ValidatorException if the language of the schema could not be guessed.
152: */
153: protected String detectGrammar(Source source) throws IOException,
154: SAXException, ValidatorException {
155: /* Prepare a key, and try to get the cached copy of the schema */
156: String uri = source.getURI();
157: String key = this .getClass().getName() + "@" + source.getURI();
158:
159: CachedGrammar grammar = null;
160: grammar = (CachedGrammar) this .store.get(key);
161:
162: /* If the schema was found verify its validity and optionally clear */
163: if (grammar != null) {
164: if (grammar.validity == null) {
165: /* Why did we cache it in the first place? */
166: this .logger.warn("Grammar for " + uri
167: + " has null validity");
168: this .store.remove(key);
169: grammar = null;
170: } else if (grammar.validity.isValid() != SourceValidity.VALID) {
171: if (this .logger.isDebugEnabled()) {
172: this .logger.debug("Grammar for " + uri
173: + " no longer valid");
174: }
175: this .store.remove(key);
176: grammar = null;
177: } else if (this .logger.isDebugEnabled()) {
178: this .logger.debug("Valid cached grammar " + grammar
179: + " for " + uri);
180: }
181: }
182:
183: /* If the schema was not cached or was cleared, parse and cache it */
184: if (grammar != null) {
185: return grammar.grammar;
186: } else {
187: String language = super .detectGrammar(source);
188: SourceValidity validity = source.getValidity();
189: if (validity != null) {
190: if (validity.isValid() == SourceValidity.VALID) {
191: this .store.store(key, new CachedGrammar(validity,
192: language));
193: }
194: }
195: return language;
196: }
197: }
198:
199: /**
200: * <p>A simple inner class associating grammar languages and source validity
201: * for caching of schema grammar detection.</p>
202: */
203: private static final class CachedGrammar {
204: private final SourceValidity validity;
205: private final String grammar;
206:
207: private CachedGrammar(SourceValidity validity, String grammar) {
208: this.validity = validity;
209: this.grammar = grammar;
210: }
211: }
212: }
|