blob: bbaf0f386fc9fcc58b7e672290bf35b2dac1248e [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;
import java.io.File;
import java.net.URL;
import java.util.Map;
import java.util.Vector;
import org.eclipse.persistence.exceptions.SessionLoaderException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.sessions.factories.PersistenceEntityResolver;
import org.eclipse.persistence.internal.sessions.factories.SessionsFactory;
import org.eclipse.persistence.internal.sessions.factories.XMLSessionConfigProject_11_1_1;
import org.eclipse.persistence.internal.sessions.factories.XMLSessionConfigToplinkProject;
import org.eclipse.persistence.internal.sessions.factories.model.SessionConfigs;
import org.eclipse.persistence.oxm.XMLContext;
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;
import org.eclipse.persistence.sessions.Session;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Provide a mechanism for loading Session configuration XML files.
* This is used by the SessionManager to define how to find and load a Session from a sessions XML file.
* The sessions XML file is typically deployed in the applications jar (ejb-jar) and named sessions.xml in the /META-INF directory.
* Several loading options are provided,
* <ul>
* <li> resourceName : The resource path and file name to the sessions XML file,
* default is /sessions.xml or /META-INF/sessions.xml. (ensure "/" is used, not "\").
* A file path can also be provided, although a resource is typically used.
* <li> shouldLogin : Define if the loaded session should be connected, default true.
* <li> shouldRefresh : Define if the loaded session should be refreshed from the file,
* (this old session will be disconnected) default false.
* <li> classLoader : Define the class loader that should be used to find the resource.
* This loader will also be used as the loaded session's class loader. This should be the application's class loader.
* Default is the ConversionManager loader, which is thread-based.
* <li> shouldCheckClassLoader : Defines if the session will be refreshed from the file if the class loader requesting the load,
* is different than the loaded session's class loader. This can be used to handle re-deployment.
* </ul>
*
* @since TopLink 10.1.3
* @author Guy Pelletier
*/
public class XMLSessionConfigLoader {
protected String resourceName;
/** Stores the resource path to provide a better error message if the load fails. */
protected String resourcePath = DEFAULT_RESOURCE_NAME;
/** Stores the name of the Session in the sessions XML file desired to be loaded. */
protected String sessionName = "default";
/** Define if the loaded session should be connected, default true. */
protected boolean shouldLogin = true;
/** Define if the loaded session should be refreshed from the file. */
protected boolean shouldRefresh = false;
/** Define the class loader that should be used to find the resource. */
protected ClassLoader classLoader;
/** Defines if the session will be refreshed from the file if the class loader requesting the load is different than the loaded session's class loader. */
protected boolean shouldCheckClassLoader = false;
/** Stores any exceptions that occurred to provide all the exceptions up front if the load fails. */
protected Vector<Throwable> exceptionStore;
/** Used to store the entity resolver to validate the XML schema when parsing. */
protected PersistenceEntityResolver entityResolver;
public static final String ECLIPSELINK_SESSIONS_SCHEMA = "org/eclipse/persistence/eclipselink_sessions_2.1.xsd";
protected static final String DEFAULT_RESOURCE_NAME = "sessions.xml";
protected static final String DEFAULT_RESOURCE_NAME_IN_META_INF = "META-INF/sessions.xml";
/** Cache the creation and initialization of the Session XML mapping project. */
protected static final Project project = new XMLSessionConfigProject_11_1_1();
/** Cache the creation and initialization of the Session XML mapping project. */
protected static Project getProject() {
return project;
}
/**
* PUBLIC:
* This constructor is used when the file resource named 'sessions.xml' will
* be parsed for configuration.
*/
public XMLSessionConfigLoader() {
this(DEFAULT_RESOURCE_NAME);
}
/**
* PUBLIC:
* This constructor is used when passing in the resource name of the
* configuration file that should be parsed
*/
public XMLSessionConfigLoader(String resourceName) {
this.resourceName = resourceName;
this.exceptionStore = new Vector<>();
this.entityResolver = new PersistenceEntityResolver();
this.classLoader = ConversionManager.getDefaultManager().getLoader();
}
/**
* INTERNAL:
* Will return the the resource name with full path of the resource file.
*/
public String getResourcePath() {
return this.resourcePath;
}
/**
* INTERNAL:
*/
public Vector<Throwable> getExceptionStore() {
return this.exceptionStore;
}
/**
* PUBLIC:
* Returns the resource name we are trying to load.
*/
public String getResourceName() {
return this.resourceName;
}
/**
* PUBLIC:
* Set the resource name we are trying to load.
*/
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
/**
* PUBLIC:
* Returns the name of the Session in the sessions XML file desired to be loaded.
*/
public String getSessionName() {
return this.sessionName;
}
/**
* PUBLIC:
* Set the name of the Session in the sessions XML file desired to be loaded.
*/
public void setSessionName(String sessionName) {
this.sessionName = sessionName;
}
/**
* PUBLIC:
* Return if the loaded session should be connected.
*/
public boolean shouldLogin() {
return shouldLogin;
}
/**
* PUBLIC:
* Set if the loaded session should be connected.
*/
public void setShouldLogin(boolean shouldLogin) {
this.shouldLogin = shouldLogin;
}
/**
* PUBLIC:
* Return if the loaded session should be refreshed from the file.
*/
public boolean shouldRefresh() {
return shouldRefresh;
}
/**
* PUBLIC:
* Set if the loaded session should be refreshed from the file.
*/
public void setShouldRefresh(boolean shouldRefresh) {
this.shouldRefresh = shouldRefresh;
}
/**
* PUBLIC:
* Return if the session will be refreshed from the file if the class loader requesting the load is different than the loaded session's class loader.
*/
public boolean shouldCheckClassLoader() {
return shouldCheckClassLoader;
}
/**
* PUBLIC:
* Set if the session will be refreshed from the file if the class loader requesting the load is different than the loaded session's class loader.
*/
public void setShouldCheckClassLoader(boolean shouldCheckClassLoader) {
this.shouldCheckClassLoader = shouldCheckClassLoader;
}
/**
* PUBLIC:
* Return the class loader that should be used to find the resource.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* PUBLIC:
* Set the class loader that should be used to find the resource.
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* INTERNAL:
* This method instantiates the parser and builds the document based on the
* schema. If the document is loaded without errors, then the configs are
* converted to sessions and stored on the session manager and true is
* returned to indicate success.
*/
public boolean load(SessionManager sessionManager, ClassLoader loader) {
Document document = loadDocument(loader);
if(getExceptionStore().isEmpty()){
if (document.getDocumentElement().getTagName().equals("sessions")) {
return buildSessionConfigs(sessionManager,loader,document,getProject());
}
}else{
//upon this time, we knew this could be either toplink sessions.xml or invalid eclipse session.xml.
if(document.getDocumentElement().getTagName().equals("toplink-sessions")){
return buildSessionConfigs(sessionManager,loader,document,new XMLSessionConfigToplinkProject());
}else{
// Throw the exceptions we encountered
throw SessionLoaderException.finalException(getExceptionStore());
}
}
// 9.0.4 session.xml, return false to indicate we should load with the XMLLoader
return false;
}
private boolean buildSessionConfigs(SessionManager sessionManager, ClassLoader loader,Document document, Project project){
// No errors occurred, unmasrshal the document which will return a
// SessionConfigs containing 0 or more SessionConfigs and send
// them through the factory to create actual Sessions
XMLContext context = new XMLContext(project);
XMLUnmarshaller unmarshaller = context.createUnmarshaller();
SessionConfigs configs = (SessionConfigs)unmarshaller.unmarshal(document);
SessionsFactory factory = new SessionsFactory();
Map<String, Session> sessions = factory.buildSessionConfigs(configs, loader);
for (Map.Entry<String, Session> entry: sessions.entrySet()) {
// Only add the session if missing.
if (!sessionManager.getSessions().containsKey(entry.getKey())) {
sessionManager.addSession(entry.getKey(), entry.getValue());
}
}
return true;
}
/**
* INTERNAL:
* This method is to be used to load config objects for the Mapping Workbench
* only. Do not call this method.
*/
public SessionConfigs loadConfigsForMappingWorkbench(ClassLoader loader) {
return loadConfigsForMappingWorkbench(loader, true);
}
/**
* INTERNAL:
* This method is to be used to load config objects for the Mapping Workbench
* only. Do not call this method.
*/
public SessionConfigs loadConfigsForMappingWorkbench(ClassLoader loader, boolean validate) {
Document document = loadDocument(loader, validate);
if (getExceptionStore().isEmpty()) {
if (document.getDocumentElement().getTagName().equals("sessions")) {
// No errors occurred, unmarshal the document which will return a
// SessionConfigs containing 0 or more SessionConfigs
XMLContext context = new XMLContext(getProject());
XMLUnmarshaller unmarshaller = context.createUnmarshaller();
return (SessionConfigs)unmarshaller.unmarshal(document);
}else{
// 9.0.4 session.xml or invalid xml format.
throw SessionLoaderException.InvalidSessionXML();
}
} else {
if (document.getDocumentElement().getTagName().equals("toplink-sessions")) {
// No errors occurred, unmarshal the document which will return a
// SessionConfigs containing 0 or more SessionConfigs
XMLContext context = new XMLContext(new XMLSessionConfigToplinkProject());
XMLUnmarshaller unmarshaller = context.createUnmarshaller();
return (SessionConfigs)unmarshaller.unmarshal(document);
}else{
// Throw the exceptions we encountered
throw SessionLoaderException.finalException(getExceptionStore());
}
}
}
/**
* INTERNAL:
* Load a session.xml document. The error handler will capture all the
* errors and allow for a document to be returned.
*/
protected Document loadDocument(ClassLoader loader) {
return loadDocument(loader, SessionManager.shouldUseSchemaValidation());
}
/**
* INTERNAL:
* Load a session.xml document. The error handler will capture all the
* errors and allow for a document to be returned.
*/
protected Document loadDocument(ClassLoader loader, boolean validate) {
URL inURL = loader.getResource(this.resourceName);
// Also allow a file reference.
File inFile = new File(this.resourceName);
if (inURL == null) {
// If loading the default resource name and it is not found,
// look in the META-INF directory.
if (this.resourceName.equals(DEFAULT_RESOURCE_NAME)) {
inURL = loader.getResource(DEFAULT_RESOURCE_NAME_IN_META_INF);
}
if ((inURL == null) && (!inFile.exists())) {
throw ValidationException.noSessionsXMLFound(this.resourceName);
}
}
if (inURL == null) {
this.resourcePath = inFile.getAbsolutePath();
} else {
// Stored the loaded resource path, used in exception string if the
// resource is not found or if there are errors in the resource.
this.resourcePath = inURL.getPath();
}
XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
XMLParser parser = xmlPlatform.newXMLParser();
if (validate) {
parser.setValidationMode(XMLParser.SCHEMA_VALIDATION);
} else {
parser.setValidationMode(XMLParser.NONVALIDATING);
}
parser.setWhitespacePreserving(false);
parser.setXMLSchema(loader.getResource(ECLIPSELINK_SESSIONS_SCHEMA));
parser.setEntityResolver(this.entityResolver);
parser.setErrorHandler(new XMLSessionConfigLoaderErrorHandler());
if (inURL == null) {
return parser.parse(inFile);
} else {
return parser.parse(inURL);
}
}
/**
* INTERNAL:
* <p><b>Purpose</b>: Provide a mechanism to swallow all parsing errors
*
* @see ErrorHandler
* @since TopLink 10.1.3
* @author Guy Pelletier
*/
public class XMLSessionConfigLoaderErrorHandler implements ErrorHandler {
/**
* Default constructor.
*/
public XMLSessionConfigLoaderErrorHandler() {
}
@Override
public void warning(SAXParseException e) throws SAXException {
getExceptionStore().add(SessionLoaderException.failedToParseXML(ExceptionLocalization.buildMessage("parsing_warning"), e.getLineNumber(), e.getColumnNumber(), e));
}
@Override
public void error(SAXParseException e) throws SAXException {
getExceptionStore().add(e);
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
getExceptionStore().add(e);
}
}
}