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.util.Collections;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import org.apache.avalon.framework.activity.Disposable;
025: import org.apache.avalon.framework.activity.Initializable;
026: import org.apache.avalon.framework.activity.Startable;
027: import org.apache.avalon.framework.configuration.Configurable;
028: import org.apache.avalon.framework.configuration.Configuration;
029: import org.apache.avalon.framework.configuration.ConfigurationException;
030: import org.apache.avalon.framework.context.Context;
031: import org.apache.avalon.framework.context.ContextException;
032: import org.apache.avalon.framework.context.Contextualizable;
033: import org.apache.avalon.framework.logger.LogEnabled;
034: import org.apache.avalon.framework.logger.Logger;
035: import org.apache.avalon.framework.parameters.Parameterizable;
036: import org.apache.avalon.framework.parameters.Parameters;
037: import org.apache.avalon.framework.service.ServiceException;
038: import org.apache.avalon.framework.service.ServiceSelector;
039: import org.apache.avalon.framework.service.Serviceable;
040: import org.apache.avalon.framework.thread.ThreadSafe;
041: import org.apache.cocoon.components.validation.SchemaParser;
042:
043: /**
044: * <p>The default implementation of the {@link Validator} interface provides
045: * core management for a number of {@link SchemaParser} instances.</p>
046: *
047: * <p>Given the simplicity of this implementation, only {@link SchemaParser}s
048: * implementing the {@link ThreadSafe} interface can be managed, and they can be
049: * accessed directly (via its name) through the methods specified by the
050: * {@link ServiceSelector} interface.</p>
051: *
052: * <p>That said, normally selection would occur using the methods declared by the
053: * {@link AbstractValidator} class and implemented here.</p>
054: *
055: */
056: public class DefaultValidator extends AbstractValidator implements
057: ServiceSelector, ThreadSafe, Contextualizable, Initializable,
058: Disposable, Configurable {
059:
060: /** <p>A {@link Map} associating {@link SchemaParser}s with their names.</p> */
061: private final Map components = Collections
062: .synchronizedMap(new HashMap());
063: /** <p>A {@link Map} associating component names with grammars.</p> */
064: private final Map grammars = Collections
065: .synchronizedMap(new HashMap());
066:
067: /** <p>The configured {@link Context} instance.</p> */
068: private Context context = null;
069: /** <p>The configured {@link Configuration} instance.</p> */
070: private Configuration conf = null;
071:
072: /**
073: * <p>Create a new {@link DefaultValidator} instance.</p>
074: */
075: public DefaultValidator() {
076: super ();
077: }
078:
079: /**
080: * <p>Contextualize this instance.</p>
081: */
082: public void contextualize(Context context) throws ContextException {
083: this .context = context;
084: }
085:
086: /**
087: * <p>Configure this instance.</p>
088: */
089: public void configure(Configuration conf)
090: throws ConfigurationException {
091: this .conf = conf;
092: }
093:
094: /**
095: * <p>Initialize this instance.</p>
096: */
097: public void initialize() throws Exception {
098: this .logger.debug("Initializing " + this .getClass().getName());
099:
100: if (this .logger == null)
101: throw new IllegalStateException("Null logger");
102: if (this .context == null)
103: throw new IllegalStateException("Null context");
104: if (this .manager == null)
105: throw new IllegalStateException("Null manager");
106: if (this .conf == null)
107: throw new IllegalStateException("Null configuration");
108:
109: Configuration configurations[] = this .conf
110: .getChildren("schema-parser");
111: this .logger.debug("Configuring " + configurations.length
112: + " schema parsers" + " from "
113: + this .conf.getLocation());
114:
115: /* Iterate through all the sub-confiuration instances */
116: for (int x = 0; x < configurations.length; x++)
117: try {
118: final Configuration configuration = configurations[x];
119: final String className = configuration
120: .getAttribute("class");
121: final String selectionKey = configuration
122: .getAttribute("name");
123:
124: /* Check that we don't have a duplicate schema parser name in configs */
125: if (this .components.containsKey(selectionKey)) {
126: String message = "Duplicate schema parser \""
127: + selectionKey + "\"";
128: throw new ConfigurationException(message,
129: configuration);
130: }
131:
132: /* Dump some debugging information, just in case */
133: this .logger.debug("Configuring schema parser "
134: + selectionKey + " as " + className + " from "
135: + configuration.getLocation());
136:
137: /* Try to load and instantiate the SchemaParser */
138: final SchemaParser schemaParser;
139: try {
140: /* Load the class */
141: final Class clazz = Class.forName(className);
142:
143: /* ClassCastExceptions normally don't come with messages (darn) */
144: if (!SchemaParser.class.isAssignableFrom(clazz)) {
145: String message = "Class " + className
146: + " doesn't implement the "
147: + SchemaParser.class.getName()
148: + " interface";
149: throw new ConfigurationException(message,
150: configuration);
151: }
152:
153: /* We only support ThreadSafe SchemaParser instances */
154: if (!ThreadSafe.class.isAssignableFrom(clazz)) {
155: String message = "Class " + className
156: + " doesn't implement the "
157: + ThreadSafe.class.getName()
158: + " interface";
159: throw new ConfigurationException(message,
160: configuration);
161: }
162:
163: /* Instantiate and set up the new SchemaParser */
164: schemaParser = (SchemaParser) clazz.newInstance();
165: this .setupComponent(selectionKey, schemaParser,
166: configuration);
167:
168: } catch (ConfigurationException exception) {
169: throw exception;
170: } catch (Exception exception) {
171: String message = "Unable to instantiate SchemaParser "
172: + className;
173: throw new ConfigurationException(message,
174: configuration, exception);
175: }
176:
177: /* Store this instance (and report about it) */
178: this .components.put(selectionKey, schemaParser);
179: this .logger.debug("SchemaParser \"" + selectionKey
180: + "\" instantiated" + " from class "
181: + className);
182:
183: /* Analyze the grammars provided by the current SchemaParser */
184: String grammars[] = schemaParser.getSupportedGrammars();
185: if (grammars == null)
186: continue;
187:
188: /* Iterate through the grammars and store them (default lookup) */
189: for (int k = 0; k < grammars.length; k++) {
190: if (this .grammars.containsKey(grammars[k])) {
191: if (this .logger.isDebugEnabled()) {
192: this .logger
193: .debug("SchemaParser \""
194: + selectionKey
195: + "\" "
196: + "supports grammar \""
197: + grammars[k]
198: + "\" but is not the default provider");
199: }
200: continue;
201: }
202:
203: /* Noone yet supports this grammar, make this the default */
204: this .grammars.put(grammars[k], selectionKey);
205: if (this .logger.isDebugEnabled()) {
206: this .logger.debug("SchemaParser \""
207: + selectionKey + "\" is the "
208: + "default grammar provider for "
209: + grammars[k]);
210: }
211: }
212:
213: } catch (Exception exception) {
214: /* Darn, we had an exception instantiating one of the components */
215: exception.printStackTrace();
216: this .logger.fatalError(
217: "Exception creating schema parsers", exception);
218:
219: /* Dispose all previously stored component instances */
220: Iterator iterator = this .components.values().iterator();
221: while (iterator.hasNext())
222: try {
223: this .decommissionComponent(iterator.next());
224: } catch (Exception nested) {
225: this .logger.fatalError(
226: "Error decommissioning component",
227: nested);
228: }
229:
230: /* Depending on the exception type, re-throw it or wrap it */
231: if (exception instanceof ConfigurationException) {
232: throw exception;
233: } else {
234: Configuration configuration = configurations[x];
235: String message = "Unable to setup SchemaParser declared at ";
236: message += configuration.getLocation();
237: throw new ConfigurationException(message,
238: configuration, exception);
239: }
240: }
241: }
242:
243: /**
244: * <p>Dispose of this instance.</p>
245: *
246: * <p>All sub-components initialized previously will be disposed of when this
247: * method is called.</p>
248: */
249: public void dispose() {
250: Iterator iterator = this .components.values().iterator();
251: while (iterator.hasNext())
252: try {
253: this .decommissionComponent(iterator.next());
254: } catch (Exception exception) {
255: this .logger.fatalError(
256: "Error decommissioning component", exception);
257: }
258: }
259:
260: /* =========================================================================== */
261: /* IMPLEMENTATION OF METHODS SPECIFIED BY THE ABSTRACTVALIDATOR CLASS */
262: /* =========================================================================== */
263:
264: /**
265: * <p>Attempt to acquire a {@link SchemaParser} interface able to understand
266: * the grammar language specified.</p>
267: *
268: * @param grammar the grammar language that must be understood by the returned
269: * {@link SchemaParser}
270: * @return a {@link SchemaParser} instance or <b>null</b> if none was found able
271: * to understand the specified grammar language.
272: */
273: protected SchemaParser lookupParserByGrammar(String grammar) {
274: if (this .grammars.containsKey(grammar)) {
275: return this .lookupParserByName((String) this .grammars
276: .get(grammar));
277: }
278: return null;
279: }
280:
281: /**
282: * <p>Attempt to acquire a {@link SchemaParser} interface associated with the
283: * specified instance name.</p>
284: *
285: * @param name the name associated with the {@link SchemaParser} to be returned.
286: * @return a {@link SchemaParser} instance or <b>null</b> if none was found.
287: */
288: protected SchemaParser lookupParserByName(String name) {
289: if (this .isSelectable(name))
290: try {
291: return (SchemaParser) this .select(name);
292: } catch (ServiceException exception) {
293: return null;
294: }
295: return null;
296: }
297:
298: /**
299: * <p>Release a previously acquired {@link SchemaParser} instance back to its
300: * original component manager.</p>
301: *
302: * <p>This method is supplied in case solid implementations of this class relied
303: * on the {@link ServiceManager} to manage {@link SchemaParser}s instances.</p>
304: *
305: * @param parser the {@link SchemaParser} whose instance is to be released.
306: */
307: protected void releaseParser(SchemaParser parser) {
308: this .release(parser);
309: }
310:
311: /* =========================================================================== */
312: /* IMPLEMENTATION OF THE METHODS SPECIFIED BY THE SERVICESELECTOR INTERFACE */
313: /* =========================================================================== */
314:
315: /**
316: * <p>Select a subcomponent ({@link SchemaParser}) associated with the specified
317: * selection key (its configured "name").</p>
318: */
319: public Object select(Object selectionKey) throws ServiceException {
320: /* Look up for the specified component and return it if found */
321: if (this .components.containsKey(selectionKey)) {
322: return this .components.get(selectionKey);
323: }
324:
325: /* Fail miserably */
326: String message = "No component associated with " + selectionKey;
327: throw new ServiceException((String) selectionKey, message);
328: }
329:
330: /**
331: * <p>Check whether a subcomponent ({@link SchemaParser}) associated with the
332: * specified selection key (its configured "name") is selectable in
333: * this {@link ServiceSelector} instance.</p>
334: */
335: public boolean isSelectable(Object selectionKey) {
336: return this .components.containsKey(selectionKey);
337: }
338:
339: /**
340: * <p>Release a subcomponent ({@link SchemaParser}) instance previously selected
341: * from this {@link ServiceSelector} instance.</p>
342: */
343: public void release(Object component) {
344: // We don't need to do anything in this method.
345: }
346:
347: /* =========================================================================== */
348: /* SUBCOMPONENTS (SCHEMA PARSERS) LIFECYCLE MANAGEMENT METHODS */
349: /* =========================================================================== */
350:
351: /**
352: * <p>Manage the instantiation lifecycle of a specified component.</p>
353: */
354: private Object setupComponent(String name, Object component,
355: Configuration conf) throws Exception {
356: boolean initialized = false;
357: boolean started = false;
358:
359: try {
360: if (component instanceof LogEnabled) {
361: Logger logger = this .logger.getChildLogger(name);
362: ((LogEnabled) component).enableLogging(logger);
363: }
364:
365: if (component instanceof Contextualizable) {
366: ((Contextualizable) component)
367: .contextualize(this .context);
368: }
369:
370: if (component instanceof Serviceable) {
371: ((Serviceable) component).service(this .manager);
372: }
373:
374: if (component instanceof Configurable) {
375: ((Configurable) component).configure(conf);
376: }
377:
378: if (component instanceof Parameterizable) {
379: Parameters parameters = Parameters
380: .fromConfiguration(conf);
381: ((Parameterizable) component).parameterize(parameters);
382: }
383:
384: if (component instanceof Initializable) {
385: ((Initializable) component).initialize();
386: initialized = true;
387: }
388:
389: if (component instanceof Startable) {
390: ((Startable) component).start();
391: started = true;
392: }
393:
394: return component;
395:
396: } catch (Exception exception) {
397: if ((started) && (component instanceof Startable))
398: try {
399: ((Startable) component).stop();
400: } catch (Exception nested) {
401: this .logger.fatalError("Error stopping component",
402: nested);
403: }
404: if ((initialized) && (component instanceof Disposable))
405: try {
406: ((Disposable) component).dispose();
407: } catch (Exception nested) {
408: this .logger.fatalError("Error disposing component",
409: nested);
410: }
411: throw exception;
412: }
413: }
414:
415: /**
416: * <p>Manage the distruction lifecycle of a specified component.</p>
417: */
418: private void decommissionComponent(Object component)
419: throws Exception {
420: try {
421: if (component instanceof Startable)
422: ((Startable) component).stop();
423: } finally {
424: if (component instanceof Disposable)
425: ((Disposable) component).dispose();
426: }
427: }
428: }
|