blob: fbb1762a8d39d0e6b684179a69a7dfacdbb0d34a [file] [log] [blame]
/*
*
* Derby - Class org.apache.derbyTesting.junit.DatabasePropertyTestSetup
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package org.apache.derbyTesting.junit;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;
import junit.framework.Test;
import org.apache.derbyTesting.functionTests.util.SQLStateConstants;
/**
* Test decorator to set a set of database properties on setUp
* and restore them to the previous values on tearDown.
*
*/
public class DatabasePropertyTestSetup extends BaseJDBCTestSetup {
private Properties newValues;
private Properties oldValues;
private final boolean staticProperties;
/**
* Decorator to change the lock time outs.
* If either time is less than zero then that property is
* not modified by this decorator.
* The change is implemented by an instanceof DatabasePropertyTestSetup
* and thus is reset by the tearDown method.
*
* @param test Test to decorate
* @param deadlockTime Time in seconds for derby.locks.deadlockTimeout.
* @param waitTime Time in seconds for derby.locks.waitTimeout
*/
public static Test setLockTimeouts(Test test, int deadlockTime, int waitTime)
{
final Properties properties = new Properties();
if (deadlockTime >= 0)
{
properties.setProperty("derby.locks.deadlockTimeout",
Integer.toString(deadlockTime));
}
if (waitTime >= 0) {
properties.setProperty("derby.locks.waitTimeout",
Integer.toString(waitTime));
}
// No change, no point to the decorator.
if (properties.isEmpty())
return test;
return new DatabasePropertyTestSetup(test, properties, true);
}
/**
* Decorate a test so that the database has authentication enabled
* using the BUILTIN provider and the set of users passed in.
* The password for each user is set to the user's name as set
* in the users array with the value of passwordToken appended.
* <BR>
* The user names in the users array are treated as SQL identifiers
* since that is the defined behaviour for derby.user.username.
* This means that the quoted identifiers can be passed, examples
* are (users[i] is shown as the contents of the Java string) with
* a password suffix of T63:
* <UL>
* <LI>users[i]=fred - normal user name FRED, password fredT63
* <LI>users[i]=FRED - normal user name FRED, passeword FREDT63
* <LI>users[i]="FRED" - normal user name FRED, passeword "FREDT63"
* <LI>users[i]="fred" - normal user name fred, passeword "fredT63"
* </UL>
* Thus with a quoted identifier the password will include the quotes.
* Note bug DERBY-3150 exists which means that the normalized user name
* to password mapping does not exist, thus a connection request must be
* made with the values passed in the users array, not any other form of the
* name.
* <BR>
* The decorated test can use BaseJDBCTestCase.openUserConnection(String user)
* method to simplify using authentication.
* <P>
* Assumption is that no authentication was enabled upon entry.
* <P>
* Current user is set to the first user in the list users[0].
* <P>
* The authentication is removed by the decorator's tearDown method.
* @param test Test to be decorated.
* @param users Set of users for authentication.
* @return Decorated test.
*/
public static Test builtinAuthentication(Test test, String[] users,
String passwordToken)
{
final Properties userProps = new Properties();
final Properties authProps = new Properties();
authProps.setProperty("derby.connection.requireAuthentication", "true");
authProps.setProperty("derby.authentication.provider", "BUILTIN");
for (int i = 0; i < users.length; i++)
{
String user = users[i];
userProps.setProperty("derby.user." + user,
TestConfiguration.getPassword(user, passwordToken));
}
// Need to setup the decorators carefully.
// Need execution in this order:
// 1) add user definitions (no authentication enabled)
// 2) switch to a valid user
// 3) enable authentication with database reboot
// 4) disable authentication with database reboot
// 5) switch back to previous user
// 6) remove user defintions.
//
// Combining steps 1,3 does not work as no shutdown request
// is possible for step 4 as no valid users would be defined!
//
// Note the decorators are executed in order from
// outer (added last) to inner.
test = new DatabasePropertyTestSetup(test, authProps, true);
test = new ChangeUserSetup(test, users[0],
TestConfiguration.getPassword(users[0], passwordToken),
passwordToken);
test = new DatabasePropertyTestSetup(test, userProps, false);
return test;
}
/**
* Decorate a test so that the database has authentication enabled
* using the BUILTIN provider and the set of users passed in.
* The password for each user is set to the user's name with
* the value of passwordToken appended.
* <BR>
* The decorated test can use BaseJDBCTestCase.openUserConnection(String user)
* method to simplify using authentication.
* <P>
* Assumption is that no authentication was enabled upon entry.
* <P>
* Current user is set to the first user in the list users[0].
* <P>
* In contrast to plain builtinAuthentication, here the
* authentication nor users are *NOT* removed by the decorator's
* tearDown method.
* @param test Test to be decorated.
* @param users Set of users for authentication.
* @return Decorated test.
*/
public static Test builtinAuthenticationNoTeardown(Test test, String[] users,
String passwordToken)
{
final Properties userProps = new Properties();
final Properties authProps = new Properties();
authProps.setProperty("derby.connection.requireAuthentication", "true");
authProps.setProperty("derby.authentication.provider", "BUILTIN");
for (int i = 0; i < users.length; i++)
{
String user = users[i];
userProps.setProperty("derby.user." + user,
TestConfiguration.getPassword(user, passwordToken));
}
test = getNoTeardownInstance(test, authProps, true);
test = new ChangeUserSetup(test, users[0],
TestConfiguration.getPassword(users[0], passwordToken),
passwordToken);
test = getNoTeardownInstance(test, userProps, false);
return test;
}
static DatabasePropertyTestSetup getNoTeardownInstance(
Test test, Properties p, boolean staticp)
{
return new DatabasePropertyTestSetup(test, p, staticp) {
protected void tearDown()
throws java.lang.Exception {
// We don't want to reset the properties, but we should
// still clear the reference to the default connection to
// allow it to be garbage collected.
clearConnection();
}
};
}
/**
* Decorate a test so that it sets a single database property
* at setUp and resets it at tearDown. Shorthand for
* using DatabasePropertyTestSetup when only a single property is needed.
* Does not perform a reboot of the database.
*/
public static Test singleProperty(Test test, String property, String value)
{
return singleProperty(test, property, value, false);
}
/**
* Decorate a test so that it sets a single database property
* at setUp and resets it at tearDown. Shorthand for
* using DatabasePropertyTestSetup when only a single property is needed.
* Optinally reboots the database.
*/
public static Test singleProperty(Test test, String property, String value,
boolean reboot)
{
final Properties properties = new Properties();
properties.setProperty(property, value);
return new DatabasePropertyTestSetup(test, properties, reboot);
}
/**
* Create a test decorator that sets and restores the passed
* in properties. Assumption is that the contents of
* properties and values will not change during execution.
* @param test test to be decorated
* @param newValues properties to be set
*/
public DatabasePropertyTestSetup(Test test,
Properties newValues)
{
this(test, newValues, false);
}
/**
* Create a test decorator that sets and restores the passed
* in properties. Assumption is that the contents of
* properties and values will not change during execution.
* @param test test to be decorated
* @param newValues properties to be set
* @param staticProperties True if database needs to be shutdown after
* setting properties in setUp() and tearDown method().
*/
public DatabasePropertyTestSetup(Test test,
Properties newValues, boolean staticProperties)
{
super(test);
this.newValues = newValues;
this.oldValues = new Properties();
this.staticProperties = staticProperties;
}
/**
* For each property store the current value and
* replace it with the new value, unless there is no change.
*/
protected void setUp()
throws java.lang.Exception
{
setProperties(newValues);
if (staticProperties) {
TestConfiguration.getCurrent().shutdownDatabase();
}
}
/**
* Revert the properties to their values prior to the setUp call.
*/
protected void tearDown()
throws java.lang.Exception
{
Connection conn = getConnection();
try {
clearProperties(conn);
} catch (SQLException sqle) {
// To try to prevent the error situation of DERBY-5686, which
// cascades to many test failures, catch ERROR 25502, and if it occurs
// try to gather some information, close the connection,
// and retry the clearing of the properties on a new connection
if (sqle.getSQLState().equals("25502")) {
// firstly, check on the state of the connection when we
// get this error
System.out.println("Apparently this is a read-only connection in teardown()? Get some data:");
System.out.println("conn.isClosed: " + conn.isClosed());
System.out.println("conn.isReadOnly: " + conn.isReadOnly());
System.out.println("conn.getHoldability: " + conn.getHoldability());
System.out.println("conn.getTransactionIsolation: " + conn.getTransactionIsolation());
System.out.println("conn.getAutoCommit: " + conn.getAutoCommit());
// now try to close the connection, then try open a new one,
// and try to executeUpdate again.
try {
conn.close();
} catch (SQLException isqle) {
if (sqle.getSQLState()=="25001")
{
// the transaction is still active. let's commit what we have.
conn.commit();
conn.close();
} else {
System.out.println("close failed - see SQLState.");
throw sqle;
}
}
Connection conn2 = getConnection();
// check if this second connection is read-only
if (conn2.isReadOnly())
{
System.out.println("Sorry, conn2 is also read-only, won't retry");
// give up
throw sqle;
}
else
{
// retry
System.out.println("retrying clearing the Properties");
clearProperties(conn2);
}
}
else if(!sqle.getSQLState().equals(SQLStateConstants.PROPERTY_UNSUPPORTED_CHANGE))
throw sqle;
}
// and then reset nay old values which will cause the commit.
setProperties(oldValues);
super.tearDown();
newValues = null;
oldValues = null;
if (staticProperties) {
TestConfiguration.getCurrent().shutdownDatabase();
}
}
private void clearProperties(Connection conn) throws SQLException
{
conn.setAutoCommit(false);
CallableStatement setDBP = conn.prepareCall(
"CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, NULL)");
// Clear all the system properties set by the new set
// that will not be reset by the old set. Ignore any
// invalid property values.
for (Enumeration e = newValues.propertyNames(); e.hasMoreElements();)
{
String key = (String) e.nextElement();
if (oldValues.getProperty(key) == null)
{
setDBP.setString(1, key);
setDBP.executeUpdate();
}
}
}
private void setProperties(Properties values) throws SQLException
{
Connection conn = getConnection();
try {
attemptSetProperties(values, conn);
} catch (SQLException sqle) {
// To try to prevent the error situation of DERBY-5686, which
// cascades to many test failures, catch ERROR 25502, and if it occurs
// try to gather some information, close the connection,
// and retry the clearing of the properties on a new connection
if (sqle.getSQLState().equals("25502")) {
// firstly, check on the state of the connection when we
// get this error
System.out.println("Apparently this is a read-only connection? Get some data:");
System.out.println("conn.isClosed: " + conn.isClosed());
System.out.println("conn.isReadOnly: " + conn.isReadOnly());
System.out.println("conn.getHoldability: " + conn.getHoldability());
System.out.println("conn.getTransactionIsolation: " + conn.getTransactionIsolation());
System.out.println("conn.getAutoCommit: " + conn.getAutoCommit());
// now try to close the connection, then try open a new one,
// and try to executeUpdate again.
try {
conn.close();
} catch (SQLException isqle) {
if (sqle.getSQLState()=="25001")
{
// the transaction is still active. let's commit what we have.
conn.commit();
conn.close();
} else {
System.out.println("close failed - see SQLState.");
throw sqle;
}
}
Connection conn2 = getConnection();
// check if this second connection is read-only
if (conn2.isReadOnly())
{
System.out.println("Sorry, conn2 is also read-only, won't retry");
// give up
throw sqle;
}
else
{
// retry
System.out.println("retrying to set the Properties");
attemptSetProperties(values, conn2);
}
}
}
}
private void attemptSetProperties(Properties values, Connection coonn) throws SQLException
{
Connection conn = getConnection();
conn.setAutoCommit(false);
PreparedStatement getDBP = conn.prepareStatement(
"VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY(?)");
CallableStatement setDBP = conn.prepareCall(
"CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, ?)");
for (Enumeration e = values.propertyNames(); e.hasMoreElements();)
{
final String key = (String) e.nextElement();
final String value = values.getProperty(key);
getDBP.setString(1, key);
ResultSet rs = getDBP.executeQuery();
rs.next();
String old = rs.getString(1);
rs.close();
boolean change;
if (old != null)
{
// set, might need to be changed.
change = !old.equals(value);
// If we are not processing the oldValues
// then store in the oldValues. Reference equality is ok here.
if (change && (values != oldValues))
oldValues.setProperty(key, old);
}
else {
// notset, needs to be set
change = true;
}
if (change) {
setDBP.setString(1, key);
setDBP.setString(2, value);
setDBP.executeUpdate();
}
}
conn.commit();
getDBP.close();
setDBP.close();
conn.close();
}
}