de.betterform.connector.SchemaValidator.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.connector.SchemaValidator.java

Source

/*
 * Copyright (c) 2012. betterFORM Project - http://www.betterform.de
 * Licensed under the terms of BSD License
 */

package de.betterform.connector;

import de.betterform.xml.dom.DOMUtil;
import de.betterform.xml.ns.NamespaceConstants;
import de.betterform.xml.ns.NamespaceResolver;
import de.betterform.xml.xforms.exception.XFormsException;
import de.betterform.xml.xforms.model.Model;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.net.URI;
import java.util.StringTokenizer;

/**
 * The SchemaValidator can validate the instance node according to its XMLSchema
 * <p>the schema(s) must be specified in the "schema" attribute of the model.</p>
 *
 * @author <a href="mailto:soframel@users.sourceforge.net">Sophie Ramel</a>
 */

public class SchemaValidator {
    /**
     * The logger.
     */
    private static Log LOGGER = LogFactory.getLog(SchemaValidator.class);

    public SchemaValidator() {
    }

    /**
     * validate the instance according to the schema specified on the model
     *
     * @return false if the instance is not valid
     */
    public boolean validateSchema(Model model, Node instance) throws XFormsException {
        boolean valid = true;
        String message;
        if (LOGGER.isDebugEnabled())
            LOGGER.debug("SchemaValidator.validateSchema: validating instance");

        //needed if we want to load schemas from Model + set it as "schemaLocation" attribute
        String schemas = model.getElement().getAttributeNS(NamespaceConstants.XFORMS_NS, "schema");
        if (schemas != null && !schemas.equals("")) {
            //          valid=false;

            //add schemas to element
            //shouldn't it be done on a copy of the doc ?
            Element el = null;
            if (instance.getNodeType() == Node.ELEMENT_NODE)
                el = (Element) instance;
            else if (instance.getNodeType() == Node.DOCUMENT_NODE)
                el = ((Document) instance).getDocumentElement();
            else {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("instance node type is: " + instance.getNodeType());
            }

            String prefix = NamespaceResolver.getPrefix(el, NamespaceConstants.XMLSCHEMA_INSTANCE_NS);
            //test if with targetNamespace or not
            //if more than one schema : namespaces are mandatory ! (optional only for 1)
            StringTokenizer tokenizer = new StringTokenizer(schemas, " ", false);
            String schemaLocations = null;
            String noNamespaceSchemaLocation = null;
            while (tokenizer.hasMoreElements()) {
                String token = (String) tokenizer.nextElement();
                //check that it is an URL
                URI uri = null;
                try {
                    uri = new java.net.URI(token);
                } catch (java.net.URISyntaxException ex) {
                    if (LOGGER.isDebugEnabled())
                        LOGGER.debug(token + " is not an URI");
                }

                if (uri != null) {
                    String ns;
                    try {
                        ns = this.getSchemaNamespace(uri);

                        if (ns != null && !ns.equals("")) {
                            if (schemaLocations == null)
                                schemaLocations = ns + " " + token;
                            else
                                schemaLocations = schemaLocations + " " + ns + " " + token;

                            ///add the namespace declaration if it is not on the instance?
                            //TODO: how to know with which prefix ?
                            String nsPrefix = NamespaceResolver.getPrefix(el, ns);
                            if (nsPrefix == null) { //namespace not declared !
                                LOGGER.warn("SchemaValidator: targetNamespace " + ns + " of schema " + token
                                        + " is not declared in instance: declaring it as default...");
                                el.setAttributeNS(NamespaceConstants.XMLNS_NS, NamespaceConstants.XMLNS_PREFIX, ns);
                            }
                        } else if (noNamespaceSchemaLocation == null)
                            noNamespaceSchemaLocation = token;
                        else { //we have more than one schema without namespace
                            LOGGER.warn("SchemaValidator: There is more than one schema without namespace !");
                        }
                    } catch (Exception ex) {
                        LOGGER.warn(
                                "Exception while trying to load schema: " + uri.toString() + ": " + ex.getMessage(),
                                ex);
                        //in case there was an exception: do nothing, do not set the schema
                    }
                }
            }
            //write schemaLocations found
            if (schemaLocations != null && !schemaLocations.equals(""))
                el.setAttributeNS(NamespaceConstants.XMLSCHEMA_INSTANCE_NS, prefix + ":schemaLocation",
                        schemaLocations);
            if (noNamespaceSchemaLocation != null)
                el.setAttributeNS(NamespaceConstants.XMLSCHEMA_INSTANCE_NS, prefix + ":noNamespaceSchemaLocation",
                        noNamespaceSchemaLocation);

            //save and parse the doc
            ValidationErrorHandler handler = null;
            File f;
            try {
                //save document
                f = File.createTempFile("instance", ".xml");
                f.deleteOnExit();
                TransformerFactory trFact = TransformerFactory.newInstance();
                Transformer trans = trFact.newTransformer();
                DOMSource source = new DOMSource(el);
                StreamResult result = new StreamResult(f);
                trans.transform(source, result);
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Validator.validateSchema: file temporarily saved in " + f.getAbsolutePath());

                //parse it with error handler to validate it
                handler = new ValidationErrorHandler();
                SAXParserFactory parserFact = SAXParserFactory.newInstance();
                parserFact.setValidating(true);
                parserFact.setNamespaceAware(true);
                SAXParser parser = parserFact.newSAXParser();
                XMLReader reader = parser.getXMLReader();

                //validation activated
                reader.setFeature("http://xml.org/sax/features/validation", true);
                //schema validation activated
                reader.setFeature("http://apache.org/xml/features/validation/schema", true);
                //used only to validate the schema, not the instance
                //reader.setFeature( "http://apache.org/xml/features/validation/schema-full-checking", true);
                //validate only if there is a grammar
                reader.setFeature("http://apache.org/xml/features/validation/dynamic", true);

                parser.parse(f, handler);
            } catch (Exception ex) {
                LOGGER.warn("Validator.validateSchema: Exception in XMLSchema validation: " + ex.getMessage(), ex);
                //throw new XFormsException("XMLSchema validation failed. "+message);
            }

            //if no exception
            if (handler != null && handler.isValid())
                valid = true;
            else {
                message = handler.getMessage();
                //TODO: find a way to get the error message displayed
                throw new XFormsException("XMLSchema validation failed. " + message);
            }

            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Validator.validateSchema: result=" + valid);

        }

        return valid;
    }

    /**
     * method to get the target namespace of a schema given by an URI:
     * it opens the schema at the given URI, anf looks for the "targetNamespace" attribute
     *
     * @param uri the URI of the schema
     * @return the targetNamespace of the schema, or null of none was found
     */
    private String getSchemaNamespace(URI uri) throws Exception {
        String ns = null;

        //load schema
        File schemaFile = new File(uri);
        Document doc = DOMUtil.parseXmlFile(schemaFile, true, false);
        if (doc != null) {
            Element schema = doc.getDocumentElement();
            ns = schema.getAttributeNS(NamespaceConstants.XMLSCHEMA_NS, "targetNamespace");
            if (ns == null || ns.equals("")) //try without NS !
                ns = schema.getAttribute("targetNamespace");
        } else
            LOGGER.warn("Schema " + uri.toString() + " could not be parsed");

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("SchemaValidator.getSchemaNamespace for schema " + uri.toString() + ": " + ns);

        return ns;
    }

    /**
     * SAX error handler for XMLSchema validation
     * TODO: transform Xerces error messages so that they are more "user-friendly"
     */
    class ValidationErrorHandler extends DefaultHandler {
        private boolean valid;
        private String message;

        public ValidationErrorHandler() {
            valid = true;
            message = null;
        }

        public void error(SAXParseException exception) throws SAXException {
            allErrors(exception);
        }

        public void fatalError(SAXParseException exception) throws SAXException {
            allErrors(exception);
        }

        public void warning(SAXParseException exception) throws SAXException {
            allErrors(exception);
        }

        public void allErrors(SAXParseException exception) throws SAXException {
            valid = false;
            if (message == null || message.equals(""))
                message = exception.getMessage();
            else
                message = message + "\n" + exception.getMessage();
            if (LOGGER.isDebugEnabled())
                LOGGER.debug("validation error: " + exception.getMessage());
        }

        public boolean isValid() {
            return valid;
        }

        public String getMessage() {
            return message;
        }
    }

}