blob: ea0701e6a574bd9f39c026d5d67ecb6ad30a2e0d [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;
import java.util.*;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.*;
import java.io.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.localization.*;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.logging.SessionLog;
/**
* <p>
* <b>Purpose</b>:Use this Connector to build a java.sql.Connection in the
* "standard" fashion, via the DriverManager.
*
* @author Big Country
* @since TOPLink/Java 2.1
*/
public class DefaultConnector implements Connector {
protected String driverClassName;
protected String driverURLHeader;
protected String databaseURL;
/** cache up the driver class to speed up reconnects */
protected Class<?> driverClass;
/** cache up the instantiated Driver to speed up reconnects */
protected Driver driver;
/** Save whether we have switched to direct connect (sql.Driver.connect()) mode */
private boolean connectDirectly = false;
/**
* PUBLIC:
* Construct a Connector with default settings
* The database URL will still need to be set.
*/
public DefaultConnector() {
}
/**
* PUBLIC:
* Construct a Connector with the specified settings.
*/
public DefaultConnector(String driverClassName, String driverURLHeader, String databaseURL) {
this.initialize(driverClassName, driverURLHeader, databaseURL);
}
/**
* INTERNAL:
* Clone the connector.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (Exception exception) {
throw new InternalError("Clone failed");
}
}
/**
* INTERNAL:
* Connect with the specified properties and session. Return the Connection.
* @return java.sql.Connection
*/
@Override
public Connection connect(Properties properties, Session session) throws DatabaseException {
// if driver class name is given, ensure the driver has been loaded and registered
if (this.driverClassName != null && this.driverClass == null) {
this.loadDriverClass(session);
}
String connectionString = this.getConnectionString();
// verify if connection URL is not null and empty
if(connectionString == null || connectionString.trim().equals("")) {
throw DatabaseException.unableToAcquireConnectionFromDriverException(
this.driverClassName, null, connectionString);
}
SQLException driverManagerException = null;
if (this.shouldUseDriverManager(properties, session)) {
try {
return DriverManager.getConnection(this.getConnectionString(), properties);
} catch (SQLException sqlException) {
driverManagerException = sqlException;
if (session != null) {
((AbstractSession)session).log(SessionLog.FINER, SessionLog.CONNECTION, "connect_drivermanager_fail");
((AbstractSession)session).logThrowable(SessionLog.FINER, SessionLog.CONNECTION, sqlException);
}
}
}
if (this.driverClass == null) {
throw DatabaseException.sqlException(driverManagerException, (org.eclipse.persistence.internal.sessions.AbstractSession) session, true);
}
// try driver class directly
// Save secondary exception state and don't clear original exception
boolean wrongDriverExceptionOccurred = false;
try {
// A return of null indicates wrong type of driver, an SQLException means connection problems
Connection directConnection = this.directConnect(properties);
if (null == directConnection) {
// This connection failed specifically because of a wrong or unknown driver
if (null != driverManagerException) {
wrongDriverExceptionOccurred = true;
// Nest root exception
throw DatabaseException.unableToAcquireConnectionFromDriverException(driverManagerException,
getDriverClassName(), properties.getProperty("user"), getDatabaseURL());
} else {
throw DatabaseException.unableToAcquireConnectionFromDriverException(
getDriverClassName(), driverClass.getCanonicalName(), getDatabaseURL());
}
} else {
if (null != driverManagerException) {
// If this connection succeeded where the previous DriverManager connection failed - save state and continue
connectDirectly = true;
}
}
return directConnection;
} catch (DatabaseException directConnectException) {
if (driverManagerException != null && !wrongDriverExceptionOccurred) {
throw DatabaseException.sqlException(driverManagerException, (AbstractSession) session, true);
} else {
throw directConnectException;
}
}
}
/**
* INTERNAL:
* Indicates whether DriverManager should be used.
* @return boolean
*/
public boolean shouldUseDriverManager(Properties properties, Session session) {
return !connectDirectly && ((session == null) || session.getServerPlatform().shouldUseDriverManager());
}
/**
* INTERNAL:
* Connect directly - without using DriverManager. Return the Connection.
* @return java.sql.Connection
*/
protected Connection directConnect(Properties properties) throws DatabaseException {
if(this.driver == null) {
this.instantiateDriver();
}
try {
return this.driver.connect(this.getConnectionString(), properties);
} catch (SQLException exception) {
this.clearDriverClassAndDriver();
throw DatabaseException.sqlException(exception, true);
}
}
/**
* PUBLIC:
* Return the JDBC connection string.
* This is a combination of the driver-specific URL header and the database URL.
*/
public String getConnectionString() {
if(this.getDriverURLHeader() == null) {
return this.getDatabaseURL();
} else if(this.getDatabaseURL() == null) {
return this.getDriverURLHeader();
}
return this.getDriverURLHeader() + this.getDatabaseURL();
}
/**
* PUBLIC:
* Provide the details of my connection information. This is primarily for JMX runtime services.
* @return java.lang.String
*/
@Override
public String getConnectionDetails() {
return this.getConnectionString();
}
/**
* PUBLIC:
* The database URL is the JDBC URL for the database server.
* The driver header is <i>not</i> be included in this URL
* (e.g. "dbase files"; not "jdbc:odbc:dbase files").
*/
public String getDatabaseURL() {
return databaseURL;
}
/**
* PUBLIC:
* The driver class is the name of the Java class for the JDBC driver being used
* (e.g. "sun.jdbc.odbc.JdbcOdbcDriver").
*/
public String getDriverClassName() {
return driverClassName;
}
/**
* PUBLIC:
* The driver URL header is the string predetermined by the JDBC driver to be
* part of the URL connection string, (e.g. "jdbc:odbc:").
* This is required to connect to the database.
*/
public String getDriverURLHeader() {
return driverURLHeader;
}
/**
* INTERNAL:
* Initialize the connector with the specified settings.
*/
protected void initialize(String driverClassName, String driverURLHeader, String databaseURL) {
this.setDriverClassName(driverClassName);
this.setDriverURLHeader(driverURLHeader);
this.setDatabaseURL(databaseURL);
}
/**
* INTERNAL:
* Ensure that the driver has been loaded and registered with the
* DriverManager. Just loading the class should cause the static
* initialization code to do the necessary registration.
* Return the loaded driver Class.
*/
protected void loadDriverClass(Session session) throws DatabaseException {
// CR#... The correct class loader must be used to load the class,
// not that Class.forName must be used to initialize the class a simple loadClass may not.
try {
if(session != null) {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
driverClass = AccessController.doPrivileged(new PrivilegedClassForName<>(this.getDriverClassName(), true, session.getPlatform().getConversionManager().getLoader()));
} catch (PrivilegedActionException exception) {
throw DatabaseException.configurationErrorClassNotFound(this.getDriverClassName());
}
} else {
driverClass = PrivilegedAccessHelper.getClassForName(this.getDriverClassName(), true, session.getPlatform().getConversionManager().getLoader());
}
} else {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
driverClass = AccessController.doPrivileged(new PrivilegedClassForName<>(this.getDriverClassName(), true, ConversionManager.getDefaultManager().getLoader()));
} catch (PrivilegedActionException exception) {
throw DatabaseException.configurationErrorClassNotFound(this.getDriverClassName()); }
} else {
driverClass = PrivilegedAccessHelper.getClassForName(this.getDriverClassName(), true, ConversionManager.getDefaultManager().getLoader());
}
}
} catch (ClassNotFoundException exception) {
clearDriverClassAndDriver();
throw DatabaseException.configurationErrorClassNotFound(this.getDriverClassName());
}
}
/**
* PUBLIC:
* The database URL is the JDBC URL for the database server.
* The driver header is <i>not</i> be included in this URL
* (e.g. "dbase files"; not "jdbc:odbc:dbase files").
*/
public void setDatabaseURL(String databaseURL) {
this.databaseURL = databaseURL;
}
/**
* PUBLIC:
* The driver class is the name of the Java class for the JDBC driver being used
* (e.g. "sun.jdbc.odbc.JdbcOdbcDriver").
*/
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
// if driver class name changed then discard the cached driver class and driver.
clearDriverClassAndDriver();
}
/**
* PUBLIC:
* The driver URL header is the string predetermined by the JDBC driver to be
* part of the URL connection string, (e.g. "jdbc:odbc:").
* This is required to connect to the database.
*/
public void setDriverURLHeader(String driverURLHeader) {
this.driverURLHeader = driverURLHeader;
}
/**
* PUBLIC:
* Print connection string.
*/
@Override
public String toString() {
return Helper.getShortClassName(getClass()) + "(" + getConnectionString() + ")";
}
/**
* INTERNAL:
* Print something useful on the log.
*/
@Override
public void toString(PrintWriter writer) {
writer.println(ToStringLocalization.buildMessage("datasource_URL", null) + "=> \"" + this.getConnectionString() + "\"");
}
/**
* INTERNAL:
* Instantiate the Driver.
*/
protected void instantiateDriver() throws DatabaseException {
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try{
this.driver = (Driver)AccessController.doPrivileged(new PrivilegedNewInstanceFromClass<>(this.driverClass));
}catch (PrivilegedActionException ex){
if (ex.getCause() instanceof IllegalAccessException){
throw (IllegalAccessException)ex.getCause();
}else if (ex.getCause() instanceof InstantiationException){
throw (InstantiationException)ex.getCause();
}
throw (RuntimeException)ex.getCause();
}
}else{
this.driver = (Driver)PrivilegedAccessHelper.newInstanceFromClass(this.driverClass);
}
} catch (InstantiationException ie) {
this.clearDriverClassAndDriver();
throw DatabaseException.configurationErrorNewInstanceInstantiationException(ie, driverClass);
} catch (IllegalAccessException iae) {
this.clearDriverClassAndDriver();
throw DatabaseException.configurationErrorNewInstanceIllegalAccessException(iae, driverClass);
}
}
/**
* INTERNAL:
* Discard the cached driver class and driver.
*/
public void clearDriverClassAndDriver() {
this.driverClass = null;
this.driver = null;
}
}