blob: d144c55886be4b78c002b06078d5f2ebf267c99e [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:
// ailitchev - Bug 256296: Reconnect fails when session loses connectivity;
// Bug 256284: Closing anEMF where the database is unavailable results in deployment exception on redeploy.
package org.eclipse.persistence.testing.framework;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Logger;
/*
* DriverWrapper works together with ConnectionWrapper.
* This pair of classes allows to intercept calls to Driver and Connection methods.
* DriverWrapper can imitate both the db going down (or losing network connection to it),
* and coming back up (or network connection restored).
* ConnectionWrapper have the same functionality, but applied to a single connection.
*
* There's an example of ConnectionWrapper usage in EntityManagerJUnitTestSuite:
* testEMCloseAndOpen and testEMFactoryCloseAndOpen.
*
* To use DriverWrapper in jpa, initialize DriverWrapper - all methods are static - with the original driver name.
*
* Then create EMF, using PersistenceUnit properties, substitute:
* the original driver class for DriverWrapper (optional) and
* the original url for "coded" (':' substituted for'*') url (otherwise DriverWrapper would not be called - the original driver would).
* If created EMF uses DriverWrapper it will print out connection string that looks like "jdbc*oracle*thin*@localhost*1521*orcl".
*
* DriverWrapper just passes all the calls to the wrapped driver (the one passed to initialize method):
* connect method wraps the created connection into ConnectionWrapper and caches them.
*
* Unless any of "break" methods called it should function in exactly the same way as original driver, the same for connections, too.
*
* But of course real fun is in breaking:
* breakDriver breaks all the methods (that throw SQLException) of the Driver;
* brealOldConnections breaks all connections produced so far;
* breakNewConnections ensures that all newly produced connections are broken.
*
* Any method called on broken connection results in SQLException.
*
* The simple scenarios used in both EntityManagerJUnitTestSuite tests is imitation of db going down, then coming back:
* going down: call breakDriver and breakOldConnections (calling breakNewConnections is possible but won't add anything -
* as long as driver is broken there will be no new connections);
* coming back: call repairDriver - now new functional new connections could be created, but all old connections are still broken.
*
* Also you can also break / repair individual connection.
* If, say breakOldConnections was performed on DriwerWrapper and repair on ConnectionWrapper the chronologically last call wins.
* There's no harm in breaking (or repairing) several times in a row.
*
* You can pass custom exception string to each break method, otherwise defaults used (the string will be in SQLException, also visible in debugger).
*
* Another usage that seems useful: stepping through the code in debugger you can trigger SQLException
* to be thrown by any Connection method at will
* be setting broken flag on ConnectionWrapper to true.
*
* After the EMF using DriverWrapper is closed, call DriverWrapper.clear() to forget the wrapped driver and clear all the cached ConnectionWrappers.
*/
public class DriverWrapper implements Driver {
// the wrapped driver name
static String driverName;
// the wrapped driver
static Driver driver;
// if set to true then methods called on the driver throw exception
static boolean driverBroken;
static String driverBrokenExceptionString;
public static String defaultDriverBrokenExceptionString = "DriverWrapper: driver is broken";
// if set to true then methods called on the connections already acquired throw exception
static boolean oldConnectionsBroken;
static String oldConnectionsBrokenExceptionString;
public static String defaultOldConnectionsBrokenExceptionString = "DriverWrapper: old connections are broken";
// if set to true then methods called on the newly acquired connections will throw exception
static boolean newConnectionsBroken;
static String newConnectionsBrokenExceptionString;
public static String defaultNewConnectionsBrokenExceptionString = "DriverWrapper: new connections are broken";
// all created ConnectionWrappers are cached
static HashSet<ConnectionWrapper> connections = new HashSet();
// register with DriverManager
static {
try {
DriverManager.registerDriver(new DriverWrapper());
} catch (SQLException ex) {
throw new TestProblemException("registerDriver failed for DriverWrapper", ex);
}
}
public static String codeUrl(String url) {
return url.replace(':', '*');
}
public static String decodeUrl(String url) {
return url.replace('*', ':');
}
public static void initialize(String newDriverName) {
clear();
driverName = newDriverName;
}
public static void breakDriver() {
breakDriver(defaultDriverBrokenExceptionString);
}
public static void breakDriver(String exceptionString) {
driverBroken = true;
driverBrokenExceptionString = exceptionString;
}
public static void repairDriver() {
driverBroken = false;
driverBrokenExceptionString = null;
}
public static void breakOldConnections() {
breakOldConnections(defaultOldConnectionsBrokenExceptionString);
}
public static void breakOldConnections(String exceptionString) {
oldConnectionsBroken = true;
oldConnectionsBrokenExceptionString = exceptionString;
Iterator<ConnectionWrapper> it = connections.iterator();
while(it.hasNext()) {
it.next().breakConnection(oldConnectionsBrokenExceptionString);
}
}
public static void repairOldConnections() {
oldConnectionsBroken = false;
oldConnectionsBrokenExceptionString = null;
Iterator<ConnectionWrapper> it = connections.iterator();
while(it.hasNext()) {
it.next().repairConnection();
}
}
public static void breakNewConnections() {
breakNewConnections(defaultNewConnectionsBrokenExceptionString);
}
public static void breakNewConnections(String exceptionString) {
newConnectionsBroken = true;
newConnectionsBrokenExceptionString = exceptionString;
}
public static void repairNewConnections() {
newConnectionsBroken = false;
newConnectionsBrokenExceptionString = null;
}
public static void breakAll() {
breakDriver();
breakNewConnections();
breakOldConnections();
}
public static void repairAll() {
repairDriver();
repairNewConnections();
repairOldConnections();
}
public static void clear() {
repairAll();
Iterator<ConnectionWrapper> it = connections.iterator();
while(it.hasNext()) {
try {
it.next().close();
} catch (SQLException ex) {
//ignore
}
}
connections.clear();
driver = null;
driverName = null;
}
static Driver getDriver() {
if(driver == null) {
try {
driver = (Driver)Class.forName(driverName, true, Thread.currentThread().getContextClassLoader()).getConstructor().newInstance();
} catch (Exception ex) {
throw new TestProblemException("DriverWrapper: failed to instantiate " + driverName, ex);
}
}
return driver;
}
public static boolean driverBroken() {
return driverBroken;
}
public static String getDriverBrokenExceptionString() {
return driverBrokenExceptionString;
}
public static boolean oldConnectionsBroken() {
return oldConnectionsBroken;
}
public static String getOldConnectionsBrokenExceptionString() {
return oldConnectionsBrokenExceptionString;
}
public static boolean newConnectionsBroken() {
return newConnectionsBroken;
}
public static String getNewConnectionsBrokenExceptionString() {
return newConnectionsBrokenExceptionString;
}
/*
* The following methods implement Driver interface
*/
@Override
public Connection connect(String url, java.util.Properties info) throws SQLException {
if(driverBroken) {
throw new SQLException(getDriverBrokenExceptionString());
}
String decodedUrl = decodeUrl(url);
if(driverName != null) {
Connection internalConn = getDriver().connect(decodedUrl, info);
if (internalConn == null) {
// The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given URL.
return null;
}
ConnectionWrapper conn = new ConnectionWrapper(internalConn);
connections.add(conn);
return conn;
} else {
// non-initialized DriverWrapper should be ignored by DriverManager.
return null;
}
}
@Override
public boolean acceptsURL(String url) throws SQLException {
if(driverName != null) {
if(driverBroken) {
throw new SQLException(getDriverBrokenExceptionString());
}
String decodedUrl = decodeUrl(url);
return getDriver().acceptsURL(decodedUrl);
} else {
// non-initialized DriverWrapper should be ignored by DriverManager.
return false;
}
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException {
if(driverBroken) {
throw new SQLException(getDriverBrokenExceptionString());
}
return getDriver().getPropertyInfo(url, info);
}
@Override
public int getMajorVersion() {
return getDriver().getMajorVersion();
}
@Override
public int getMinorVersion() {
return getDriver().getMinorVersion();
}
@Override
public boolean jdbcCompliant() {
return getDriver().jdbcCompliant();
}
@Override
public Logger getParentLogger() {return null;}
}