/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * 05/16/2008-1.0M8 Guy Pelletier * - 218084: Implement metadata merging functionality between mapping file * 09/23/2008-1.1 Guy Pelletier * - 241651: JPA 2.0 Access Type support * 12/10/2008-1.1 Michael O'Brien * - 257606: Add orm.xml schema validation true/(false) flag support in persistence.xml * 03/27/2009-2.0 Guy Pelletier * - 241413: JPA 2.0 Add EclipseLink support for Map type attributes * 10/09/2012-2.5 Guy Pelletier * - 374688: JPA 2.1 Converter support * 02/14/2013-2.5 Guy Pelletier * - 338610: JPA 2.1 Functionality for Java EE 7 (JSR-338) ******************************************************************************/ package org.eclipse.persistence.internal.jpa.metadata.xml; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.Map; import java.util.Properties; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.exceptions.EclipseLinkException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.helper.XMLHelper; import org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider; import org.eclipse.persistence.oxm.XMLConstants; import org.eclipse.persistence.oxm.XMLContext; import org.eclipse.persistence.oxm.XMLUnmarshaller; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; /** * ORM.xml reader. * * @author Guy Pelletier * @since EclipseLink 1.0 */ public class XMLEntityMappingsReader { public static final String ORM_1_0_XSD = "org/eclipse/persistence/jpa/orm_1_0.xsd"; public static final String ORM_1_0_NAMESPACE = "http://java.sun.com/xml/ns/persistence/orm"; public static final String ORM_2_0_XSD = "org/eclipse/persistence/jpa/orm_2_0.xsd"; public static final String ORM_2_0_NAMESPACE = "http://java.sun.com/xml/ns/persistence/orm"; public static final String ORM_2_1_XSD = "org/eclipse/persistence/jpa/orm_2_1.xsd"; public static final String ORM_2_1_NAMESPACE = "http://xmlns.jcp.org/xml/ns/persistence/orm"; public static final String ECLIPSELINK_ORM_XSD = "org/eclipse/persistence/jpa/eclipselink_orm_2_5.xsd"; public static final String ECLIPSELINK_ORM_NAMESPACE = "http://www.eclipse.org/eclipselink/xsds/persistence/orm"; private static XMLContext m_orm1_0Project; private static XMLContext m_orm2_0Project; private static XMLContext m_orm2_1Project; private static XMLContext m_eclipseLinkOrmProject; private static Schema m_orm1_0Schema; private static Schema m_orm2_0Schema; private static Schema m_orm2_1Schema; private static Schema m_eclipseLinkOrmSchema; /** * Check the orm.xml to determine which project and schema to use. * EclipseLink ORM is used if input Reader is null */ private static Object[] determineXMLContextAndSchema(String file, Reader input, boolean validateSchema) throws Exception { Object[] context = new Object[2]; if (input == null) { // No input, default it to the eclipselink orm project. context[0] = getEclipseLinkOrmProject(); if (validateSchema) { context[1] = getEclipseLinkOrmSchema(); } } else { SAXParserFactory factory = XMLHelper.createParserFactory(false); // create a SAX parser SAXParser parser = factory.newSAXParser(); // create an XMLReader XMLReader xmlReader = parser.getXMLReader(); ORMContentHandler contentHandler = new ORMContentHandler(); xmlReader.setContentHandler(contentHandler); InputSource inputSource = new InputSource(input); xmlReader.parse(inputSource); if (contentHandler.isEclipseLink()) { context[0] = getEclipseLinkOrmProject(); if (validateSchema) { context[1] = getEclipseLinkOrmSchema(); } } else if (contentHandler.getVersion() == null || contentHandler.getVersion().indexOf("2") == -1) { context[0] = getOrm1_0Project(); if (validateSchema) { context[1] = getOrm1_0Schema(); } } else if (contentHandler.getVersion().indexOf("2.1") == -1) { context[0] = getOrm2_0Project(); if (validateSchema) { context[1] = getOrm2_0Schema(); } } else { context[0] = getOrm2_1Project(); if (validateSchema) { context[1] = getOrm2_1Schema(); } } } return context; } /** * @return the Eclipselink orm project. */ public static XMLContext getEclipseLinkOrmProject() { if (m_eclipseLinkOrmProject == null) { m_eclipseLinkOrmProject = new XMLContext(new XMLEntityMappingsMappingProject(ECLIPSELINK_ORM_NAMESPACE, ECLIPSELINK_ORM_XSD)); } return m_eclipseLinkOrmProject; } /** * @return the Eclipselink orm schema. */ public static Schema getEclipseLinkOrmSchema() throws IOException, SAXException { if (m_eclipseLinkOrmSchema == null) { m_eclipseLinkOrmSchema = loadLocalSchema(ECLIPSELINK_ORM_XSD); } return m_eclipseLinkOrmSchema; } /** * Gets a reader from given URL. */ private static InputStreamReader getInputStreamReader(URL url) throws IOException { java.net.URLConnection cnx1 = url.openConnection(); //set to false to prevent locking of jar files on Windows. EclipseLink issue 249664 cnx1.setUseCaches(false); return new InputStreamReader(cnx1.getInputStream(), "UTF-8"); } /** * @return the JPA 1.0 orm project. */ public static XMLContext getOrm1_0Project() { if (m_orm1_0Project == null) { m_orm1_0Project = new XMLContext(new XMLEntityMappingsMappingProject(ORM_1_0_NAMESPACE, ORM_1_0_XSD)); } return m_orm1_0Project; } /** * @return the JPA 1.0 orm schema. */ public static Schema getOrm1_0Schema() throws IOException, SAXException { if (m_orm1_0Schema == null) { m_orm1_0Schema = loadLocalSchema(ORM_1_0_XSD); } return m_orm1_0Schema; } /** * @return the JPA 2.0 orm project. */ public static XMLContext getOrm2_0Project() { if (m_orm2_0Project == null) { m_orm2_0Project = new XMLContext(new XMLEntityMappingsMappingProject(ORM_2_0_NAMESPACE, ORM_2_0_XSD)); } return m_orm2_0Project; } /** * @return the JPA 2.0 orm schema. */ public static Schema getOrm2_0Schema() throws IOException, SAXException { if (m_orm2_0Schema == null) { m_orm2_0Schema = loadLocalSchema(ORM_2_0_XSD); } return m_orm2_0Schema; } /** * @return the JPA 2.1 orm project. */ public static XMLContext getOrm2_1Project() { if (m_orm2_1Project == null) { m_orm2_1Project = new XMLContext(new XMLEntityMappingsMappingProject(ORM_2_1_NAMESPACE, ORM_2_1_XSD)); } return m_orm2_1Project; } /** * @return the JPA 2.1 orm schema. */ public static Schema getOrm2_1Schema() throws IOException, SAXException { if (m_orm2_1Schema == null) { m_orm2_1Schema = loadLocalSchema(ORM_2_1_XSD); } return m_orm2_1Schema; } /** * Free the project and schema objects to avoid holding onto the memory. * This can be done post-deployment to conserve memory. */ public static void clear() { m_orm1_0Project = null; m_orm2_0Project = null; m_orm2_1Project = null; m_eclipseLinkOrmProject = null; m_orm1_0Schema = null; m_orm2_0Schema = null; m_orm2_1Schema = null; m_eclipseLinkOrmSchema = null; } /** * INTERNAL: * Return whether the schema validation flag in the Persistence Unit * eclipselink.orm.validate.schema is set to true or false.<br> * The default value is false. * @param properties - PersistenceUnitInfo properties on the project * @return */ private static boolean isORMSchemaValidationPerformed(Map properties) { // Get property from persistence.xml (we are not yet parsing sessions.xml) String value = EntityManagerFactoryProvider.getConfigPropertyAsString(PersistenceUnitProperties.ORM_SCHEMA_VALIDATION, properties, "false"); // A true validation property value will override the default of false or NONVALIDATING return (value != null && value.equalsIgnoreCase("true")); } /** * Load the XML schema from the jar resource. */ protected static Schema loadLocalSchema(String schemaName) throws IOException, SAXException { URL url = XMLEntityMappingsReader.class.getClassLoader().getResource(schemaName); InputStream schemaStream = url.openStream(); try { StreamSource source = new StreamSource(url.openStream()); SchemaFactory schemaFactory = XMLHelper.createSchemaFactory(XMLConstants.SCHEMA_URL, false); Schema schema = schemaFactory.newSchema(source); return schema; } finally { schemaStream.close(); } } /** * INTERNAL: */ public static XMLEntityMappings read(String sourceName, Reader reader, ClassLoader classLoader, Map properties){ return read(sourceName, null, reader, classLoader, properties); } /** * INTERNAL: */ protected static XMLEntityMappings read(String mappingFile, Reader reader1, Reader reader2, ClassLoader classLoader, Map properties) { // Get the schema validation flag if present in the persistence unit properties boolean validateORMSchema = isORMSchemaValidationPerformed(properties); // Unmarshall JPA format. XMLEntityMappings xmlEntityMappings; try { // First need to determine which context/schema to use, JPA 1.0, 2.0 or EclipseLink orm (only latest supported) Object[] context = determineXMLContextAndSchema(mappingFile, reader1, validateORMSchema); XMLUnmarshaller unmarshaller = ((XMLContext)context[0]).createUnmarshaller(); if (validateORMSchema) { useLocalSchemaForUnmarshaller(unmarshaller, ((Schema)context[1])); } xmlEntityMappings = (XMLEntityMappings) unmarshaller.unmarshal(reader2); } catch (Exception exception) { throw ValidationException.errorParsingMappingFile(mappingFile, exception); } if (xmlEntityMappings != null) { xmlEntityMappings.setMappingFile(mappingFile); } return xmlEntityMappings; } /** * INTERNAL: * @param url * @param classLoader * @param properties - PersistenceUnitInfo properties on the project * @return * @throws IOException */ public static XMLEntityMappings read(URL url, ClassLoader classLoader, Properties properties) throws IOException { InputStreamReader reader1 = null; InputStreamReader reader2 = null; try { try { //Get separate readers as the read method below is coded to seek through both of them reader1 = getInputStreamReader(url); reader2 = getInputStreamReader(url); return read(url.toString(), reader1, reader2, classLoader, properties); } catch (UnsupportedEncodingException exception) { throw ValidationException.fatalErrorOccurred(exception); } } finally { try { if (reader1 != null) { reader1.close(); } if (reader2 != null) { reader2.close(); } } catch (IOException exception) { throw ValidationException.fileError(exception); } } } /** * This method allows you to set an XML schema on a given unmarshaller. It * will get the schema from the same classloader that loaded this class and * hence works for the case where the schema is shipped as part of EclipseLink * * @param unmarshaller * @param schemaName * @param validateORMSchema * @throws IOException * @throws SAXException */ private static void useLocalSchemaForUnmarshaller(XMLUnmarshaller unmarshaller, Schema schema) { try { unmarshaller.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { if (exception.getException() instanceof EclipseLinkException) { throw (EclipseLinkException) exception.getCause(); } } }); unmarshaller.setSchema(schema); } catch (UnsupportedOperationException ex) { // Some parsers do not support setSchema. In that case, setup validation another way. unmarshaller.setValidationMode(XMLUnmarshaller.SCHEMA_VALIDATION); } } }