blob: d235ecb2f127cfbd6f6b278bb990e7043284e512 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2018 IBM and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// 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)
// 09/06/2017-2.7 Jody Grassel
// - 521954: Eclipselink is not able to parse ORM XML files using the 2.2 schema
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 ORM_2_2_XSD = "org/eclipse/persistence/jpa/orm_2_2.xsd";
public static final String ORM_2_2_NAMESPACE = "http://xmlns.jcp.org/xml/ns/persistence/orm";
public static final String ORM_3_0_XSD = "org/eclipse/persistence/jpa/orm_3_0.xsd";
public static final String ORM_3_0_NAMESPACE = "https://jakarta.ee/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_orm2_2Project;
private static XMLContext m_orm3_0Project;
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_orm2_2Schema;
private static Schema m_orm3_0Schema;
private static Schema m_eclipseLinkOrmSchema;
private XMLEntityMappingsReader() {
}
/**
* 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().contains("1.")) {
context[0] = getOrm1_0Project();
if (validateSchema) {
context[1] = getOrm1_0Schema();
}
} else if (contentHandler.getVersion().contains("2.0")) {
context[0] = getOrm2_0Project();
if (validateSchema) {
context[1] = getOrm2_0Schema();
}
} else if (contentHandler.getVersion().contains("2.1")) {
context[0] = getOrm2_1Project();
if (validateSchema) {
context[1] = getOrm2_1Schema();
}
} else if (contentHandler.getVersion().contains("2.2")) {
context[0] = getOrm2_2Project();
if (validateSchema) {
context[1] = getOrm2_2Schema();
}
} else {
context[0] = getOrm3_0Project();
if (validateSchema) {
context[1] = getOrm3_0Schema();
}
}
}
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;
}
/**
* @return the JPA 2.2 orm project.
*/
public static XMLContext getOrm2_2Project() {
if (m_orm2_2Project == null) {
m_orm2_2Project = new XMLContext(new XMLEntityMappingsMappingProject(ORM_2_2_NAMESPACE, ORM_2_2_XSD));
}
return m_orm2_2Project;
}
/**
* @return the JPA 2.2 orm schema.
*/
public static Schema getOrm2_2Schema() throws IOException, SAXException {
if (m_orm2_2Schema == null) {
m_orm2_2Schema = loadLocalSchema(ORM_2_2_XSD);
}
return m_orm2_2Schema;
}
/**
* @return the JPA 3.0 orm project.
*/
public static XMLContext getOrm3_0Project() {
if (m_orm3_0Project == null) {
m_orm3_0Project = new XMLContext(new XMLEntityMappingsMappingProject(ORM_3_0_NAMESPACE, ORM_3_0_XSD));
}
return m_orm3_0Project;
}
/**
* @return the JPA 3.0 orm schema.
*/
public static Schema getOrm3_0Schema() throws IOException, SAXException {
if (m_orm3_0Schema == null) {
m_orm3_0Schema = loadLocalSchema(ORM_3_0_XSD);
}
return m_orm3_0Schema;
}
/**
* 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_orm2_2Project = null;
m_orm3_0Project = null;
m_eclipseLinkOrmProject = null;
m_orm1_0Schema = null;
m_orm2_0Schema = null;
m_orm2_1Schema = null;
m_orm2_2Schema = null;
m_orm3_0Schema = 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
*/
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 properties - PersistenceUnitInfo properties on the project
*/
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
*
*/
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);
}
}
}