Java tutorial
/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2001 - 2013 Object Refinery Ltd, Hitachi Vantara and Contributors.. All rights reserved. */ package org.pentaho.reporting.libraries.xmlns.parser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.base.config.DefaultConfiguration; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import org.pentaho.reporting.libraries.resourceloader.CompoundResource; import org.pentaho.reporting.libraries.resourceloader.FactoryParameterKey; import org.pentaho.reporting.libraries.resourceloader.Resource; import org.pentaho.reporting.libraries.resourceloader.ResourceCreationException; import org.pentaho.reporting.libraries.resourceloader.ResourceData; import org.pentaho.reporting.libraries.resourceloader.ResourceFactory; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.ResourceKeyCreationException; import org.pentaho.reporting.libraries.resourceloader.ResourceLoadingException; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import org.pentaho.reporting.libraries.resourceloader.loader.raw.RawResourceData; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; /** * A base-class for resource-factories that load their resources from XML files. This class provides a multiplexing * option. For this, the parser looks at the root-element of the document to be parsed and selects the most suitable * XmlFactoryModule implementation registered. * * @author Thomas Morgner * @noinspection HardCodedStringLiteral */ public abstract class AbstractXmlResourceFactory implements ResourceFactory { private static final Log logger = LogFactory.getLog(AbstractXmlResourceFactory.class); /** * A key for the content base. */ public static final String CONTENTBASE_KEY = "content-base"; private static final byte[] EMPTY_DATA = new byte[0]; private ArrayList<XmlFactoryModule> modules; private ArrayList<XmlFactoryModule> modulesFromConfiguration; private SAXParserFactory factory; /** * Default-Constructor. */ protected AbstractXmlResourceFactory() { modules = new ArrayList<XmlFactoryModule>(); modulesFromConfiguration = new ArrayList<XmlFactoryModule>(); } /** * Returns a SAX parser. * * @return a SAXParser. * @throws ParserConfigurationException if there is a problem configuring the parser. * @throws SAXException if there is a problem with the parser initialisation */ protected SAXParser getParser() throws ParserConfigurationException, SAXException { if (this.factory == null) { this.factory = SAXParserFactory.newInstance(); } return this.factory.newSAXParser(); } /** * Configures the xml reader. Use this to set features or properties before the documents get parsed. * * @param handler the parser implementation that will handle the SAX-Callbacks. * @param reader the xml reader that should be configured. */ protected void configureReader(final XMLReader reader, final RootXmlReadHandler handler) { try { reader.setProperty("http://xml.org/sax/properties/lexical-handler", handler.getCommentHandler()); } catch (final SAXException se) { // ignore .. logger.debug("Comments are not supported by this SAX implementation."); } try { reader.setFeature("http://xml.org/sax/features/xmlns-uris", true); } catch (final SAXException e) { // ignore handler.setXmlnsUrisNotAvailable(true); } try { // disable validation, as our parsers should handle that already. And we do not want to read // external DTDs that may not exist at all. reader.setFeature("http://xml.org/sax/features/validation", false); reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); reader.setFeature("http://xml.org/sax/features/external-general-entities", false); } catch (final SAXException e) { // ignore if (logger.isDebugEnabled()) { logger.debug( "Disabling external validation failed. Parsing may or may not fail with a parse error later."); } } try { reader.setFeature("http://xml.org/sax/features/namespaces", true); reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); } catch (final SAXException e) { if (logger.isDebugEnabled()) { logger.warn("No Namespace features will be available. (Yes, this is serious)", e); } else if (logger.isWarnEnabled()) { logger.warn("No Namespace features will be available. (Yes, this is serious)"); } } } /** * Creates a resource by interpreting the data given in the resource-data object. If additional datastreams need to be * parsed, the provided resource manager should be used. This method parses the given resource-data as XML stream. * * @param manager the resource manager used for all resource loading. * @param data the resource-data from where the binary data is read. * @param context the resource context used to resolve relative resource paths. * @return the parsed result, never null. * @throws ResourceCreationException if the resource could not be parsed due to syntaxctial or logical errors in the * data. * @throws ResourceLoadingException if the resource could not be accessed from the physical storage. */ public Resource create(final ResourceManager manager, final ResourceData data, final ResourceKey context) throws ResourceCreationException, ResourceLoadingException { try { final SAXParser parser = getParser(); final XMLReader reader = parser.getXMLReader(); final XmlFactoryModule[] rootHandlers = getModules(); if (rootHandlers.length == 0) { throw new ResourceCreationException( "There are no root-handlers registered for the factory for type " + getFactoryType()); } final ResourceDataInputSource input = new ResourceDataInputSource(data, manager); final ResourceKey contextKey; final long version; final ResourceKey targetKey = data.getKey(); if (context == null) { contextKey = targetKey; version = data.getVersion(manager); } else { contextKey = context; version = -1; } final RootXmlReadHandler handler = createRootHandler(manager, targetKey, rootHandlers, contextKey, version); final DefaultConfiguration parserConfiguration = handler.getParserConfiguration(); final URL value = manager.toURL(contextKey); if (value != null) { parserConfiguration.setConfigProperty(CONTENTBASE_KEY, value.toExternalForm()); } configureReader(reader, handler); reader.setContentHandler(handler); reader.setDTDHandler(handler); reader.setEntityResolver(handler.getEntityResolver()); reader.setErrorHandler(getErrorHandler()); final Map parameters = targetKey.getFactoryParameters(); final Iterator it = parameters.keySet().iterator(); while (it.hasNext()) { final Object o = it.next(); if (o instanceof FactoryParameterKey) { final FactoryParameterKey fpk = (FactoryParameterKey) o; handler.setHelperObject(fpk.getName(), parameters.get(fpk)); } } reader.parse(input); final Object createdProduct = finishResult(handler.getResult(), manager, data, contextKey); handler.getDependencyCollector().add(targetKey, data.getVersion(manager)); return createResource(targetKey, handler, createdProduct, getFactoryType()); } catch (ParserConfigurationException e) { throw new ResourceCreationException("Unable to initialize the XML-Parser", e); } catch (SAXException e) { throw new ResourceCreationException("Unable to parse the document: " + data.getKey(), e); } catch (IOException e) { throw new ResourceLoadingException("Unable to read the stream from document: " + data.getKey(), e); } } protected RootXmlReadHandler createRootHandler(final ResourceManager manager, final ResourceKey targetKey, final XmlFactoryModule[] rootHandlers, final ResourceKey contextKey, final long version) { return new MultiplexRootElementHandler(manager, targetKey, contextKey, version, rootHandlers); } /** * A method to allow to invoke the parsing without accessing the LibLoader layer. The data to be parsed is held in the * given InputSource object. * * @param manager the resource manager used for all resource loading. * @param input the raw-data given as SAX-InputSource. * @param context the resource context used to resolve relative resource paths. * @param parameters the parse parameters. * @return the parsed result, never null. * @throws ResourceCreationException if the resource could not be parsed due to syntaxctial or logical errors in * the data. * @throws ResourceLoadingException if the resource could not be accessed from the physical storage. * @throws ResourceKeyCreationException if creating the context key failed. */ public Object parseDirectly(final ResourceManager manager, final InputSource input, final ResourceKey context, final Map parameters) throws ResourceKeyCreationException, ResourceCreationException, ResourceLoadingException { try { final SAXParser parser = getParser(); final XMLReader reader = parser.getXMLReader(); final ResourceKey targetKey = manager.createKey(EMPTY_DATA); final ResourceKey contextKey; if (context == null) { contextKey = targetKey; } else { contextKey = context; } final XmlFactoryModule[] rootHandlers = getModules(); final RootXmlReadHandler handler = createRootHandler(manager, targetKey, rootHandlers, contextKey, -1); final DefaultConfiguration parserConfiguration = handler.getParserConfiguration(); final URL value = manager.toURL(contextKey); if (value != null) { parserConfiguration.setConfigProperty(CONTENTBASE_KEY, value.toExternalForm()); } configureReader(reader, handler); reader.setContentHandler(handler); reader.setDTDHandler(handler); reader.setEntityResolver(handler.getEntityResolver()); reader.setErrorHandler(getErrorHandler()); final Iterator it = parameters.keySet().iterator(); while (it.hasNext()) { final Object o = it.next(); if (o instanceof FactoryParameterKey) { final FactoryParameterKey fpk = (FactoryParameterKey) o; handler.setHelperObject(fpk.getName(), parameters.get(fpk)); } } reader.parse(input); return finishResult(handler.getResult(), manager, new RawResourceData(targetKey), contextKey); } catch (ParserConfigurationException e) { throw new ResourceCreationException("Unable to initialize the XML-Parser", e); } catch (SAXException e) { throw new ResourceCreationException("Unable to parse the document", e); } catch (IOException e) { throw new ResourceLoadingException("Unable to read the stream", e); } } /** * Returns the registered XmlFactoryModules as array. We assume that the modules are evaluated in the given order. The * modules from the configuration are listed first (highest priority, as they may be supplied by user-overrides), then * the modules that have been registered manually, where the oldest modules are returned as lowest priority elements. * * @return the modules as array. */ protected final XmlFactoryModule[] getModules() { final ArrayList<XmlFactoryModule> realModules = new ArrayList<XmlFactoryModule>(); realModules.addAll(modulesFromConfiguration); for (int i = modules.size() - 1; i >= 0; i -= 1) { final XmlFactoryModule xmlFactoryModule = modules.get(i); realModules.add(xmlFactoryModule); } return realModules.toArray(new XmlFactoryModule[realModules.size()]); } /** * Creates a Resource object for the given product. By default this returns a compound-resource that holds all the key * that identify the resources used during the content production. * * @param targetKey the target key. * @param handler the root handler used for the parsing. * @param createdProduct the created product. * @param createdType the type information for the object that has been parsed. * @return the product wrapped into a resource object. */ protected Resource createResource(final ResourceKey targetKey, final RootXmlReadHandler handler, final Object createdProduct, final Class createdType) { return new CompoundResource(targetKey, handler.getDependencyCollector(), createdProduct, createdType); } /** * Finishes up the result. This can be used for general clean up and post-parse initializaion of the result. The * default implementation does nothing and just returns the object itself. * * @param res the parsed resource. * @param manager the resource manager that was used to load the resource. * @param data the data object from where the resource is loaded. * @param context the context that resolves relative resource paths. * @return the parsed resource. * @throws ResourceCreationException if the post initialization fails. * @throws ResourceLoadingException if loading external resources failed with an IO error. */ protected Object finishResult(final Object res, final ResourceManager manager, final ResourceData data, final ResourceKey context) throws ResourceCreationException, ResourceLoadingException { return res; } /** * Returns the configuration that should be used to initialize this factory. * * @return the configuration for initializing the factory. */ protected abstract Configuration getConfiguration(); /** * Loads all XmlFactoryModule-implementations from the given configuration. * * @see #getConfiguration() */ public void initializeDefaults() { final String type = getFactoryType().getName(); final String prefix = ResourceFactory.CONFIG_PREFIX + type; final Configuration config = getConfiguration(); final Iterator itType = config.findPropertyKeys(prefix); while (itType.hasNext()) { final String key = (String) itType.next(); final String modClass = config.getConfigProperty(key); final XmlFactoryModule maybeFactory = ObjectUtilities.loadAndInstantiate(modClass, AbstractXmlResourceFactory.class, XmlFactoryModule.class); if (maybeFactory == null) { continue; } modulesFromConfiguration.add(maybeFactory); } } /** * Registers a factory module for being used during the parsing. If the factory module does not return a result that * matches the factory's type, the parsing will always fail. * * @param factoryModule the factory module. * @throws NullPointerException if the module given is null. */ public void registerModule(final XmlFactoryModule factoryModule) { if (factoryModule == null) { throw new NullPointerException(); } modules.add(factoryModule); } /** * Returns the XML-Error handler that should be registered with the XML parser. By default, this returns a logger. * * @return the error handler. */ protected ErrorHandler getErrorHandler() { return new LoggingErrorHandler(); } }