org.hibernate.internal.util.xml.MappingReader.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.internal.util.xml.MappingReader.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.internal.util.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URL;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.hibernate.InvalidMappingException;
import org.hibernate.internal.CoreMessageLogger;

import org.jboss.logging.Logger;

import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.dom4j.io.STAXEventReader;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Handles reading mapping documents, both {@code hbm} and {@code orm} varieties.
 *
 * @author Steve Ebersole
 */
public class MappingReader {
    private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class,
            MappingReader.class.getName());

    public static final MappingReader INSTANCE = new MappingReader();

    /**
     * Disallow direct instantiation.
     * <p/>
     * Eventually we perhaps need to have this configurable by the "configuration" and simply reference it
     * from there (registry).  This would allow, for example, injection of the entity resolver to use as
     * instance state.
     */
    private MappingReader() {
    }

    public XmlDocument readMappingDocument(InputSource source, Origin origin) {
        XMLEventReader staxReader = buildStaxEventReader(source, origin);
        try {
            return read(staxReader, origin);
        } finally {
            try {
                staxReader.close();
            } catch (Exception ignore) {
            }
        }
    }

    private XMLEventReader buildStaxEventReader(InputSource source, Origin origin) {
        XMLEventReader reader = null;

        if (source.getByteStream() != null) {
            try {
                reader = staxFactory().createXMLEventReader(source.getByteStream());
            } catch (XMLStreamException e) {
                throw new XmlInfrastructureException(
                        "Unable to create stax reader, origin = " + toLoggableString(origin), e);
            }
        } else if (source.getCharacterStream() != null) {
            try {
                reader = staxFactory().createXMLEventReader(source.getCharacterStream());
            } catch (XMLStreamException e) {
                throw new XmlInfrastructureException(
                        "Unable to create stax reader, origin = " + toLoggableString(origin), e);
            }
        }
        // todo : try to interpret the InputSource SystemId or Origin path?

        if (reader == null) {
            throw new XmlInfrastructureException("Unable to convert SAX InputStream into StAX XMLEventReader");
        }

        // For performance we wrap the reader in a buffered reader
        return new BufferedXMLEventReader(reader);
    }

    private XMLInputFactory staxFactory;

    private XMLInputFactory staxFactory() {
        if (staxFactory == null) {
            staxFactory = buildStaxFactory();
        }
        return staxFactory;
    }

    @SuppressWarnings({ "UnnecessaryLocalVariable" })
    private XMLInputFactory buildStaxFactory() {
        XMLInputFactory staxFactory = XMLInputFactory.newInstance();
        staxFactory.setXMLResolver(LocalXmlResourceResolver.INSTANCE);
        return staxFactory;
    }

    private String toLoggableString(Origin origin) {
        return "[type=" + origin.getType() + ", name=" + origin.getName() + "]";
    }

    private static final QName ORM_VERSION_ATTRIBUTE_QNAME = new QName("version");

    private XmlDocument read(XMLEventReader staxEventReader, Origin origin) {
        XMLEvent event;
        try {
            event = staxEventReader.peek();
            while (event != null && !event.isStartElement()) {
                staxEventReader.nextEvent();
                event = staxEventReader.peek();
            }
        } catch (Exception e) {
            throw new InvalidMappingException("Error accessing stax stream", origin, e);
        }

        if (event == null) {
            throw new InvalidMappingException("Could not locate root element", origin);
        }

        final String rootElementName = event.asStartElement().getName().getLocalPart();

        if ("entity-mappings".equals(rootElementName)) {
            final Attribute attribute = event.asStartElement().getAttributeByName(ORM_VERSION_ATTRIBUTE_QNAME);
            final String explicitVersion = attribute == null ? null : attribute.getValue();
            validateMapping(SupportedOrmXsdVersion.parse(explicitVersion, origin), staxEventReader, origin);
        }

        return new XmlDocumentImpl(toDom4jDocument(staxEventReader, origin), origin);
    }

    private Document toDom4jDocument(XMLEventReader staxEventReader, Origin origin) {
        STAXEventReader dom4jStaxEventReader = new STAXEventReader();
        try {
            // the dom4j converter class is touchy about comments (aka, comments make it implode)
            // so wrap the event stream in a filtering stream to filter out comment events
            staxEventReader = new FilteringXMLEventReader(staxEventReader) {
                @Override
                protected XMLEvent filterEvent(XMLEvent event, boolean peek) {
                    return event.getEventType() == XMLStreamConstants.COMMENT ? null : event;
                }
            };

            return dom4jStaxEventReader.readDocument(staxEventReader);
        } catch (XMLStreamException e) {
            throw new InvalidMappingException("Unable to read StAX source as dom4j Document for processing", origin,
                    e);
        }
    }

    private void validateMapping(SupportedOrmXsdVersion xsdVersion, XMLEventReader staxEventReader, Origin origin) {
        final Validator validator = xsdVersion.getSchema().newValidator();
        final StAXSource staxSource;
        try {
            staxSource = new StAXSource(staxEventReader);
        } catch (XMLStreamException e) {
            throw new InvalidMappingException("Unable to generate StAXSource from mapping", origin, e);
        }

        try {
            validator.validate(staxSource);
        } catch (SAXException e) {
            throw new InvalidMappingException("SAXException performing validation", origin, e);
        } catch (IOException e) {
            throw new InvalidMappingException("IOException performing validation", origin, e);
        }
    }

    public static enum SupportedOrmXsdVersion {
        ORM_1_0("org/hibernate/jpa/orm_1_0.xsd"), ORM_2_0("org/hibernate/jpa/orm_2_0.xsd"), ORM_2_1(
                "org/hibernate/jpa/orm_2_1.xsd");

        private final String schemaResourceName;

        private SupportedOrmXsdVersion(String schemaResourceName) {
            this.schemaResourceName = schemaResourceName;
        }

        public static SupportedOrmXsdVersion parse(String name, Origin origin) {
            if ("1.0".equals(name)) {
                return ORM_1_0;
            } else if ("2.0".equals(name)) {
                return ORM_2_0;
            } else if ("2.1".equals(name)) {
                return ORM_2_1;
            }
            throw new UnsupportedOrmXsdVersionException(name, origin);
        }

        private URL schemaUrl;

        public URL getSchemaUrl() {
            if (schemaUrl == null) {
                schemaUrl = resolveLocalSchemaUrl(schemaResourceName);
            }
            return schemaUrl;
        }

        private Schema schema;

        public Schema getSchema() {
            if (schema == null) {
                schema = resolveLocalSchema(getSchemaUrl());
            }
            return schema;
        }
    }

    private static URL resolveLocalSchemaUrl(String schemaName) {
        URL url = MappingReader.class.getClassLoader().getResource(schemaName);
        if (url == null) {
            throw new XmlInfrastructureException("Unable to locate schema [" + schemaName + "] via classpath");
        }
        return url;
    }

    private static Schema resolveLocalSchema(URL schemaUrl) {

        try {
            InputStream schemaStream = schemaUrl.openStream();
            try {
                StreamSource source = new StreamSource(schemaUrl.openStream());
                SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
                return schemaFactory.newSchema(source);
            } catch (Exception e) {
                throw new XmlInfrastructureException("Unable to load schema [" + schemaUrl.toExternalForm() + "]",
                        e);
            } finally {
                try {
                    schemaStream.close();
                } catch (IOException e) {
                    LOG.debugf("Problem closing schema stream - %s", e.toString());
                }
            }
        } catch (IOException e) {
            throw new XmlInfrastructureException(
                    "Stream error handling schema url [" + schemaUrl.toExternalForm() + "]");
        }

    }

    public XmlDocument readMappingDocument(EntityResolver entityResolver, InputSource source, Origin origin) {
        return legacyReadMappingDocument(entityResolver, source, origin);
        //      return readMappingDocument( source, origin );
    }

    private XmlDocument legacyReadMappingDocument(EntityResolver entityResolver, InputSource source,
            Origin origin) {
        // IMPL NOTE : this is the legacy logic as pulled from the old AnnotationConfiguration code

        Exception failure;

        ErrorLogger errorHandler = new ErrorLogger();

        SAXReader saxReader = new SAXReader();
        saxReader.setEntityResolver(entityResolver);
        saxReader.setErrorHandler(errorHandler);
        saxReader.setMergeAdjacentText(true);
        saxReader.setValidation(true);

        Document document = null;
        try {
            // first try with orm 2.1 xsd validation
            setValidationFor(saxReader, "orm_2_1.xsd");
            document = saxReader.read(source);
            if (errorHandler.hasErrors()) {
                throw errorHandler.getErrors().get(0);
            }
            return new XmlDocumentImpl(document, origin.getType(), origin.getName());
        } catch (Exception e) {
            if (LOG.isDebugEnabled()) {
                LOG.debugf("Problem parsing XML using orm 2.1 xsd, trying 2.0 xsd : %s", e.getMessage());
            }
            failure = e;
            errorHandler.reset();

            if (document != null) {
                // next try with orm 2.0 xsd validation
                try {
                    setValidationFor(saxReader, "orm_2_0.xsd");
                    document = saxReader.read(new StringReader(document.asXML()));
                    if (errorHandler.hasErrors()) {
                        errorHandler.logErrors();
                        throw errorHandler.getErrors().get(0);
                    }
                    return new XmlDocumentImpl(document, origin.getType(), origin.getName());
                } catch (Exception e2) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debugf("Problem parsing XML using orm 2.0 xsd, trying 1.0 xsd : %s", e2.getMessage());
                    }
                    errorHandler.reset();

                    if (document != null) {
                        // next try with orm 1.0 xsd validation
                        try {
                            setValidationFor(saxReader, "orm_1_0.xsd");
                            document = saxReader.read(new StringReader(document.asXML()));
                            if (errorHandler.hasErrors()) {
                                errorHandler.logErrors();
                                throw errorHandler.getErrors().get(0);
                            }
                            return new XmlDocumentImpl(document, origin.getType(), origin.getName());
                        } catch (Exception e3) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debugf("Problem parsing XML using orm 1.0 xsd : %s", e3.getMessage());
                            }
                        }
                    }
                }
            }
        }
        throw new InvalidMappingException("Unable to read XML", origin.getType(), origin.getName(), failure);
    }

    private void setValidationFor(SAXReader saxReader, String xsd) {
        try {
            saxReader.setFeature("http://apache.org/xml/features/validation/schema", true);
            // saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true );
            if ("orm_2_1.xsd".equals(xsd)) {
                saxReader.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation",
                        "http://xmlns.jcp.org/xml/ns/persistence/orm " + xsd);
            } else {
                saxReader.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation",
                        "http://java.sun.com/xml/ns/persistence/orm " + xsd);
            }
        } catch (SAXException e) {
            saxReader.setValidation(false);
        }
    }

}