/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 | |
******************************************************************************/ | |
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; | |
} | |
public 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 | |
* @param document | |
* @param classLoader | |
* @param opmProject | |
* @return | |
*/ | |
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. | |
*/ | |
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); | |
} | |
} | |
} |