blob: 319e434f28018c73087ac14dcdddd29be0f7e52a [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 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
package org.eclipse.persistence.sessions.factories;
// javase imports
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
// EclipseLink imports
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.sessions.factories.EclipseLinkObjectPersistenceRuntimeXMLProject;
import org.eclipse.persistence.internal.sessions.factories.MissingDescriptorListener;
import org.eclipse.persistence.internal.sessions.factories.ObjectPersistenceRuntimeXMLProject;
import org.eclipse.persistence.internal.sessions.factories.ObjectPersistenceRuntimeXMLProject_11_1_1;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLLogin;
import org.eclipse.persistence.oxm.XMLUnmarshaller;
import org.eclipse.persistence.platform.xml.XMLParser;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.sessions.Project;
/**
* <p><b>Purpose</b>: Allow for a EclipseLink Mapping Workbench generated deployment XML project file to be read.
* This reader returns an instance of Project to be used to initialize a EclipseLink session.
* This class supports reading the 11g 11.1.1 and 10g 10.1.3.
*
* @since TopLink 3.0
* @author James Sutherland
*/
public class XMLProjectReader {
/** Allow for usage of schema validation to be configurable. */
protected static boolean shouldUseSchemaValidation = false; // switch back for 1.0
/** Cache the creation and initialization of the EclipseLink XML mapping project. */
protected static Project project;
public static final String SCHEMA_DIR = "org/eclipse/persistence/";
public static final String OPM_SCHEMA = "object-persistence_1_0.xsd";
public static final String ECLIPSELINK_SCHEMA = "eclipselink_persistence_map_2.3.xsd";
public static final String ECLIPSELINK_1_0_SCHEMA = "eclipselink_persistence_map_1.0.xsd";
public static final String TOPLINK_11_SCHEMA = "toplink-object-persistence_11_1_1.xsd";
public static final String TOPLINK_10_SCHEMA = "toplink-object-persistence_10_1_3.xsd";
/**
* PUBLIC:
* Return if schema validation will be used when parsing the deployment XML.
*/
public static boolean shouldUseSchemaValidation() {
return shouldUseSchemaValidation;
}
/**
* PUBLIC:
* Set if schema validation will be used when parsing the deployment XML.
* By default schema validation is on, but can be turned off if validation problems occur,
* or to improve parsing performance.
*/
public static void setShouldUseSchemaValidation(boolean value) {
shouldUseSchemaValidation = value;
}
private XMLProjectReader() {
super();
}
/**
* PUBLIC:
* Read the EclipseLink project deployment XML from the file or resource name.
* If a resource name is used the default class loader will be used to resolve the resource.
* Note the default class loader must be able to resolve the domain classes.
* Note the file must be the deployment XML, not the Mapping Workbench project file.
*/
public static Project read(String fileOrResourceName) {
return read(fileOrResourceName, null);
}
/**
* PUBLIC:
* Read the EclipseLink project deployment XML from the reader on the file.
* Note the class loader must be able to resolve the domain classes.
* Note the file must be the deployment XML, not the Mapping Workbench project file.
* This API supports 10g (10.0.3), 11g (11.1.1) formats.
*/
public static Project read(Reader reader, ClassLoader classLoader) {
// Since a reader is pass and it can only be streamed once (mark does not work)
// It must be first read into a buffer so multiple readers can be used to
// determine the format. This does not effect performance severely.
StringWriter writer;
Document document;
try {
writer = new StringWriter(4096);
char[] c = new char[4096];
int r = 0;
while ((r = reader.read(c)) != -1) {
writer.write(c, 0, r);
}
String schema = null;
if (shouldUseSchemaValidation()) {
schema = SCHEMA_DIR + ECLIPSELINK_SCHEMA;
}
// Assume the format is OPM parse the document with OPM validation on.
XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
XMLParser parser = createXMLParser(xmlPlatform, true, false, schema);
try {
document = parser.parse(new StringReader(writer.toString()));
} catch (Exception parseException) {
// If the parse fails, it may be because the format was EclipseLink 1.0
try {
if (shouldUseSchemaValidation()) {
schema = SCHEMA_DIR + ECLIPSELINK_1_0_SCHEMA;
}
parser = createXMLParser(xmlPlatform, true, false, schema);
document = parser.parse(new StringReader(writer.toString()));
} catch (Exception parseException2){
// If the parse fails, it may be because the format was 11.1.1
try {
if (shouldUseSchemaValidation()) {
schema = SCHEMA_DIR + TOPLINK_11_SCHEMA;
}
parser = createXMLParser(xmlPlatform, true, false, schema);
document = parser.parse(new StringReader(writer.toString()));
} catch (Exception parseException3){
// If the parse validation fails, it may be because the format was 904 which is
// not support in eclipselink, just not valid, through original exception.
throw parseException;
}
String version = document.getDocumentElement().getAttribute("version");
// If 10.1.3 format use old format read.
if ((version == null) || (version.indexOf("1.0") == -1)) {
throw parseException;
}
}
}
} catch (Exception exception) {
throw XMLMarshalException.unmarshalException(exception);
}
String version = document.getDocumentElement().getAttribute("version");
// If 10.1.3 format use old format read.
if (version != null) {
if (version.indexOf("10.1.3") != -1) {
return read1013Format(document, classLoader);
} else if (version.indexOf("11.1.1") != -1) {
return read1111Format(document, classLoader);
}
if (version.indexOf("TopLink") != -1) {
//default to read 11.1.1
return read1111Format(document, classLoader);
}
}
if (project == null) {
project = new EclipseLinkObjectPersistenceRuntimeXMLProject();
}
// bug261072: clone the project since readObjectPersistenceRuntimeFormat will change its datasourceLogin and Classloader
return readObjectPersistenceRuntimeFormat(document, classLoader, project.clone());
}
private static XMLParser createXMLParser(XMLPlatform xmlPlatform, boolean namespaceAware, boolean whitespacePreserving, String schema){
XMLParser parser = xmlPlatform.newXMLParser();
parser.setNamespaceAware(namespaceAware);
parser.setWhitespacePreserving(whitespacePreserving);
if (schema != null) {
parser.setValidationMode(XMLParser.SCHEMA_VALIDATION);
// Workaround for bug #3503583.
XMLSchemaResolver xmlSchemaResolver = new XMLSchemaResolver();
URL eclipselinkSchemaURL = xmlSchemaResolver.resolveURL(schema);
parser.setEntityResolver(xmlSchemaResolver);
parser.setXMLSchema(eclipselinkSchemaURL);
}
return parser;
}
/**
* PUBLIC:
* Read the EclipseLink project deployment XML from the file or resource name.
* If a resource name is used the class loader will be used to resolve the resource.
* Note the class loader must be able to resolve the domain classes.
* Note the file must be the deployment XML, not the Mapping Workbench project file.
*/
public static Project read(String fileOrResourceName, ClassLoader classLoader) {
if (fileOrResourceName.toLowerCase().indexOf(".mwp") != -1) {
throw ValidationException.invalidFileName(fileOrResourceName);
}
InputStream fileStream = null;
if (classLoader == null) {
fileStream = (new ConversionManager()).getLoader().getResourceAsStream(fileOrResourceName);
} else {
fileStream = classLoader.getResourceAsStream(fileOrResourceName);
}
if (fileStream == null) {
File file = new File(fileOrResourceName);
if (!file.exists()) {
throw ValidationException.projectXMLNotFound(fileOrResourceName, null);
}
try {
fileStream = new FileInputStream(fileOrResourceName);
} catch (FileNotFoundException exception) {
throw ValidationException.projectXMLNotFound(fileOrResourceName, exception);
}
}
InputStreamReader reader = null;
try {
try {
// Bug2631348 Only UTF-8 is supported
reader = new InputStreamReader(fileStream, "UTF-8");
} catch (UnsupportedEncodingException exception) {
throw ValidationException.fatalErrorOccurred(exception);
}
Project project = read(reader, classLoader);
return project;
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException exception) {
throw ValidationException.fileError(exception);
}
}
}
/**
* INTERNAL:
* Read the TopLink 10.1.3 deployment XML format.
*/
public static Project read1013Format(Document document, ClassLoader classLoader) {
Project opmProject = new ObjectPersistenceRuntimeXMLProject();
return readObjectPersistenceRuntimeFormat(document, classLoader, opmProject);
}
/**
* INTERNAL:
* Read the TopLink 11.1.1 deployment XML format.
*/
public static Project read1111Format(Document document, ClassLoader classLoader) {
Project opmProject = new ObjectPersistenceRuntimeXMLProject_11_1_1();
return readObjectPersistenceRuntimeFormat(document, classLoader, opmProject);
}
/**
* Read a project in the format of an ObjectPersistenceRuntimeXMLProject.
* This could include a TopLink 11.1.1 project or a TopLink 10.1.3 project
*/
public static Project readObjectPersistenceRuntimeFormat(Document document, ClassLoader classLoader, Project opmProject){
XMLLogin xmlLogin = new XMLLogin();
xmlLogin.setDatasourcePlatform(new org.eclipse.persistence.oxm.platform.DOMPlatform());
opmProject.setDatasourceLogin(xmlLogin);
// Create the OPM project.
if (classLoader != null) {
xmlLogin.getDatasourcePlatform().getConversionManager().setLoader(classLoader);
}
// Marshal OPM format.
XMLContext context = new XMLContext(opmProject);
context.getSession(Project.class).getEventManager().addListener(new MissingDescriptorListener());
XMLUnmarshaller unmarshaller = context.createUnmarshaller();
Project project = (Project)unmarshaller.unmarshal(document);
// Set the project's class loader.
if ((classLoader != null) && (project.getDatasourceLogin() != null)) {
project.getDatasourceLogin().getDatasourcePlatform().getConversionManager().setLoader(classLoader);
}
return project;
}
/**
* PUBLIC:
* Read the EclipseLink project deployment XML from the reader on the file.
* Note the default class loader must be able to resolve the domain classes.
* Note the file must be the deployment XML, not the Mapping Workbench project file.
*/
public static Project read(Reader reader) {
return read(reader, null);
}
/**
* INTERNAL:
* Workaround for bug #3503583.
* This works around a bug in the xdk in resolving relative jar based xsd references in oc4j.
*/
private static class XMLSchemaResolver implements EntityResolver {
/**
* INTERNAL:
*/
public XMLSchemaResolver() {
super();
}
/**
* INTERNAL:
* Resolve the XSD.
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (OPM_SCHEMA.equals(systemId)) {
URL url = resolveURL(SCHEMA_DIR + OPM_SCHEMA);
if (null == url) {
return null;
}
return new InputSource(url.openStream());
}
return null;
}
/**
* INTERNAL:
* Return the URL for the resource.
*/
public URL resolveURL(String resource) {
// The xsd is always in the eclipselink.jar, use our class loader.
return getClass().getClassLoader().getResource(resource);
}
}
}