org.rimudb.configuration.AbstractXmlLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.rimudb.configuration.AbstractXmlLoader.java

Source

/*
 * Copyright (c) 2008-2011 Simon Ritchie.
 * All rights reserved. 
 * 
 * This program is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU Lesser General Public License as published 
 * by the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program.  If not, see http://www.gnu.org/licenses/>.
 */
package org.rimudb.configuration;

import java.io.*;
import java.net.*;

import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.transform.dom.*;
import javax.xml.validation.*;

import org.apache.commons.logging.*;
import org.rimudb.xml.*;
import org.w3c.dom.*;
import org.xml.sax.*;

/**
 * This abstract class performs the load an XML document from either the classpath,
 * file system or a URL. 
 * 
 * It can also perform validation of the document against an array of schemas. It 
 * first attempts to validate using the name of the schema referenced in the document, 
 * but will fall back to trying to validate against the schemas returned by the
 * abstract method getSchemas().
 *
 * The load() method is responsible for loading the file. Given a filename,
 * it attempts to locate that file in several different ways. If the filename
 * begins with 'classpath:' it will attempt to load the filename as a resource. If the
 * filename begins with 'file:' or 'http:', it will attempt to load the file name as
 * a URL. If the filename begins with none of these, it will attempt to load it from
 * the local file system. If that fails, it will attempt to load it as a resource
 * from the classpath. 
 * 
 * @author Simon Ritchie
 *
 */
public abstract class AbstractXmlLoader {
    private static Log log = LogFactory.getLog(AbstractXmlLoader.class);
    private static final String SOURCE_CLASSPATH = "classpath:";

    private String filename = null;
    private boolean validateXML = false;
    private String documentSchema = null;

    public AbstractXmlLoader() {
    }

    /**
     * Return an array of schemas to validate against. Validation will be attempted
     * in the order the schemas are returned.
     * 
     * @return String[] Valid schemas
     */
    protected abstract String[] getSchemas();

    /**
     * Load the XML file using various strategies to locate it.
     * 
     * If the filename begins with 'classpath:' it will attempt to load the filename as a resource. 
     * 
     * If the filename begins with 'file:' or 'http:', it will attempt to load the file name as a URL. 
     * 
     * If the filename begins with none of these, it will attempt to load it from the local 
     * file system. If that fails, it will attempt to load it as a resource from the classpath.
     *  
     * @throws Exception
     */
    public Document load() throws Exception {
        return loadDocument(getFilename());
    }

    /**
     * Load the configuration document. First try loading from explicitly defined locations
     * (the classpath or a URL), then fall back to attemping to load as a local file and
     * finally a resource in the classpath.
     *  
     * @param filename String
     */
    private Document loadDocument(String filename) throws Exception {
        Document doc = null;

        // Explicitly load from the classpath
        if (filename.startsWith(SOURCE_CLASSPATH)) {
            String resourceFilename = filename.substring(SOURCE_CLASSPATH.length());
            doc = loadFromResource(resourceFilename);
            log.info("loadDocument(): loaded configuation from classpath");
            return doc;
        }

        // Explicitly load from a URL
        if (filename.startsWith("file:") || filename.startsWith("http:")) {
            URL url = new java.net.URL(filename);
            doc = loadFromURL(url);
            log.info("loadDocument(): loaded configuation from URL");
            return doc;
        }

        // Try loading as a file
        File file = new File(filename);
        if (file.exists()) {
            doc = loadFromFile(filename);
            log.info("loadDocument(): loaded configuation from file");
            return doc;
        }

        // Try loading from the classpath
        doc = loadFromResource(filename);
        log.info("loadDocument(): loaded configuation from classpath");
        return doc;
    }

    /**
     * Load the document from a resource in the classpath
     * 
     * @param source
     * @throws Exception
     */
    private Document loadFromResource(String filename) throws Exception {
        InputStream is = getClass().getResourceAsStream(filename);
        return loadXML(is);
    }

    /**
     * Load the document from a local file.
     * 
     * @param source
     * @throws FileNotFoundException
     * @throws Exception
     */
    private Document loadFromFile(String filename) throws Exception {
        FileInputStream is = new FileInputStream(filename);
        return loadXML(is);
    }

    /**
     * Load the document from a URL
     * 
     * @param url
     */
    private Document loadFromURL(URL url) throws Exception {
        // Open a connection to the URL
        URLConnection urlConn = url.openConnection();
        // Get the input stream
        InputStream is = urlConn.getInputStream();
        return loadXML(is);
    }

    /**
     * Set the CompoundDatabase filename.
     * 
     * @param filename String
     */
    public void setFilename(String filename) {
        this.filename = filename;
    }

    /**
     * Return the CompoundDatabase filename.
     * 
     * @return String
     */
    public String getFilename() {
        return filename;
    }

    /**
     * Set the validate XML property. This will cause the load() method to validate the
     * XML document against the XML schema.
     * 
     * @param validateXML boolean
     */
    public void setValidateXML(boolean validateXML) {
        this.validateXML = validateXML;
    }

    public boolean isValidateXML() {
        return validateXML;
    }

    private Document loadXML(InputStream is) throws Exception {
        // Load document
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);

        factory.setIgnoringElementContentWhitespace(true);
        factory.setIgnoringComments(true);
        factory.setValidating(false); // Don't use DTD validation

        DocumentBuilder docBuilder = factory.newDocumentBuilder();

        ErrorHandler eh = new StrictErrorHandler();
        docBuilder.setErrorHandler(eh);

        InputSource inputSource = new InputSource(is);
        inputSource.setPublicId(RimuDBNamespace.URI);
        inputSource.setSystemId(RimuDBNamespace.URI);

        Document document = docBuilder.parse(is);
        is.close();

        // Determine the XML schema version from the XML document without validating
        Element root = document.getDocumentElement();
        setDocumentSchema(lookupSchemaVersion(root));

        // Validate the XML document and determine the XML Schema version
        if (isValidateXML()) {
            if (getDocumentSchema() != null) {
                // Validate the document against the schema found in the document 
                SAXParseException saxParseException = validate(document, getDocumentSchema());
                if (saxParseException != null) {
                    throw saxParseException;
                }
            } else {
                setDocumentSchema(lookupSchemaByValidation(document));
            }
        }

        return document;
    }

    /**
     * Validate the schema and return the schema that matches by trial and error. If
     * the document cannot be validated against possible schemas then throw an Exception.
     * 
     * @param document Document
     * @return String
     */
    public String lookupSchemaByValidation(Document document) throws Exception {
        SAXParseException saxParseException = null;
        String[] schemas = getSchemas();
        for (int i = 0; i < schemas.length; i++) {
            saxParseException = validate(document, schemas[i]);
            if (saxParseException == null) {
                return schemas[i];
            }
        }
        if (saxParseException != null) {
            throw saxParseException;
        }

        return null;
    }

    /**
     * Determine the XML schema version from the XML document without validating.
     * 
     * @param root Element
     * @return String
     */
    public String lookupSchemaVersion(Element root) {
        String s = root.getAttribute("xsi:schemaLocation");
        if (s != null && s.length() > 0) {
            String parts[] = s.split(" ");
            if (parts != null && parts.length == 2) {
                String[] schemas = getSchemas();
                for (int i = 0; i < schemas.length; i++) {
                    if (parts[1].equals(schemas[i])) {
                        return schemas[i];
                    }
                }
            }
        }
        return null;
    }

    /**
     * @param document
     * @param compoundDbSchemaUrl
     * @return SAXParseException
     * @throws Exception 
     */
    protected SAXParseException validate(Document document, String compoundDbSchemaUrl) throws Exception {
        // Compile a schema for the XSD
        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

        // Look up the schema URL and find it's local resource file 
        URL schemaURL = null;
        String schemaPath = RimuDBNamespace.lookupInternalSchema(compoundDbSchemaUrl);
        if (schemaPath != null) {
            schemaURL = getClass().getClassLoader().getResource(schemaPath);
        } else {
            schemaURL = new URL(compoundDbSchemaUrl);
        }

        Schema schema = schemaFactory.newSchema(schemaURL);

        // Validate the document against the schema
        Validator validator = schema.newValidator();
        validator.setErrorHandler(new StrictErrorHandler());
        try {
            validator.validate(new DOMSource(document), new DOMResult());
        } catch (SAXParseException e) {
            return e;
        }

        return null;
    }

    public void setDocumentSchema(String validatedAgainstSchema) {
        documentSchema = validatedAgainstSchema;
    }

    public String getDocumentSchema() {
        return documentSchema;
    }

}