| /* |
| * 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 java.nio.charset.StandardCharsets; |
| |
| 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 { |
| // Bug2631348 Only UTF-8 is supported |
| reader = new InputStreamReader(fileStream, StandardCharsets.UTF_8); |
| |
| 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); |
| } |
| } |
| } |