Java tutorial
/* * 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); } } }