| /* |
| |
| Derby - Class org.apache.derbyTesting.functionTests.tests.upgradeTests.PhaseChanger |
| |
| 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.functionTests.tests.upgradeTests; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.sql.SQLException; |
| import java.util.Set; |
| |
| import javax.sql.DataSource; |
| |
| import junit.framework.Test; |
| |
| import org.apache.derby.shared.common.info.JVMInfo; |
| |
| import org.apache.derbyTesting.junit.BaseTestCase; |
| import org.apache.derbyTesting.junit.BaseTestSetup; |
| import org.apache.derbyTesting.junit.ClassLoaderTestSetup; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.JDBCDataSource; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| /** |
| * Decorator that sets the phase of the upgrade process |
| * for a suite of upgrade tests. |
| */ |
| final class PhaseChanger extends BaseTestSetup { |
| |
| private final int phase; |
| private final int[] version; |
| private ClassLoader loader; |
| private ClassLoader previousLoader; |
| private boolean trace = false; |
| private String upgradeAttributes="upgrade=true"; |
| |
| public PhaseChanger(Test test, int phase, |
| ClassLoader loader, int[] version, boolean useCreateOnUpgrade) { |
| super(test); |
| this.phase = phase; |
| this.loader = loader; |
| this.version = version; |
| if (useCreateOnUpgrade) { |
| upgradeAttributes += ";create=true"; |
| } |
| } |
| |
| /** |
| * Set the phase and boot the database, creating it |
| * or upgrading it as required. The thread context |
| * class loader is changed to point to the old |
| * jar files if required for the phase. |
| */ |
| protected void setUp() throws SQLException |
| { |
| UpgradeChange.phase.set(phase); |
| UpgradeChange.oldVersion.set(version); |
| |
| TestConfiguration config = TestConfiguration.getCurrent(); |
| trace = config.doTrace(); |
| if ( trace ) |
| { |
| String versStr = ((int[]) UpgradeChange.oldVersion.get())[0] |
| + "." + ((int[]) UpgradeChange.oldVersion.get())[1] |
| + "." + ((int[]) UpgradeChange.oldVersion.get())[2] |
| + "." + ((int[]) UpgradeChange.oldVersion.get())[3]; |
| BaseTestCase.traceit("Test upgrade from: " + versStr + ", phase: " |
| + UpgradeChange.PHASES[phase]); |
| if (UpgradeChange.PHASES[phase].equals("UPGRADE")) { |
| BaseTestCase.traceit("Upgrade attributes = " + upgradeAttributes); |
| } |
| } |
| |
| if (loader != null) { |
| previousLoader = Thread.currentThread().getContextClassLoader(); |
| ClassLoaderTestSetup.setThreadLoader(loader); |
| } |
| |
| DataSource ds = JDBCDataSource.getDataSource(); |
| switch (phase) |
| { |
| case UpgradeChange.PH_POST_HARD_UPGRADE: |
| // Post hard upgrade is expected to fail |
| // since the database cannot be accessed |
| // by the old version anymore. This will |
| // be explictly tested eslewhere rather than |
| // in a setup method. |
| return; |
| |
| case UpgradeChange.PH_CREATE: |
| JDBCDataSource.setBeanProperty(ds, "createDatabase", "create"); |
| break; |
| |
| case UpgradeChange.PH_HARD_UPGRADE: |
| JDBCDataSource.setBeanProperty(ds, "connectionAttributes", |
| upgradeAttributes); |
| break; |
| default: |
| break; |
| } |
| |
| // Ensure the database exists or upgrade it. |
| ds.getConnection().close(); |
| |
| } |
| |
| /** |
| * Shutdown the database engine and reset the class loader. |
| * @throws SQLException if the engine couldn't be stopped |
| */ |
| protected void tearDown() throws Exception |
| { |
| if ( trace ) BaseTestCase.traceit(" Test upgrade done."); |
| |
| // Get a handle to the old engine's ContextService if this version is |
| // affected by DERBY-23. The actual workaround for DERBY-23 must be |
| // done after the engine has been shut down, but we fetch the handle |
| // to the service before shutdown, while it's still easily available. |
| Object contextService = getDerby23ContextService(); |
| |
| DataSource ds = JDBCDataSource.getDataSource(); |
| JDBCDataSource.shutEngine(ds); |
| |
| // When shutting down the old engine for good, make sure that it's |
| // made eligible for garbage collection by working around bugs in |
| // some of the old versions. |
| if (phase == UpgradeChange.PH_POST_HARD_UPGRADE) { |
| // Workaround for DERBY-2905. Some versions don't deregister the |
| // JDBC driver when shutting down the engine. |
| deregisterDriver(); |
| |
| // Workaround for DERBY-4895, which prevented the engine classes |
| // from being garbage collected. |
| clearDerby4895ThreadLocal(); |
| } |
| |
| // Workaround for DERBY-23, continued. If this is one of the affected |
| // versions, clear the fields that prevent the engine from being |
| // garbage collected. |
| clearDerby23ThreadLocals(contextService); |
| |
| if (loader != null) |
| ClassLoaderTestSetup.setThreadLoader(previousLoader); |
| loader = null; |
| previousLoader = null; |
| |
| UpgradeChange.phase.set(null); |
| UpgradeChange.oldVersion.set(null); |
| } |
| |
| /** |
| * Make sure the JDBC driver in the class loader associated with this |
| * version is deregistered. This is a workaround for DERBY-2905, which |
| * affected Derby 10.2 - 10.7, and it is needed to make the old engine |
| * classes eligible for garbage collection. |
| */ |
| private void deregisterDriver() throws Exception |
| { |
| boolean isBetween10_2and10_8 = |
| UpgradeRun.lessThan(new int[] {10,2,0,0}, version) && |
| UpgradeRun.lessThan(version, new int[] {10,8,0,0}); |
| |
| boolean isAtLeast10_15 = |
| UpgradeRun.lessThan(new int[] {10,15,0,0}, version); |
| |
| boolean isAffectedVersion = isBetween10_2and10_8 || isAtLeast10_15; |
| |
| if (JDBC.vmSupportsJDBC3()) { |
| // DriverManager only allows deregistering of drivers from classes |
| // that live in a class loader that is able to load the driver. So |
| // create an instance of DriverUnloader in the old driver's class |
| // loader. |
| Class<?> unloader = Class.forName( |
| DriverUnloader.class.getName(), true, loader); |
| Method m = unloader.getMethod("unload", (Class[]) null); |
| Boolean res = (Boolean) m.invoke(null, (Object[]) null); |
| |
| // Don't check this on a JVM which is |
| // module aware because, in that case, the relevant driver is remote |
| // in the server. |
| if (!JVMInfo.isModuleAware()) |
| { |
| // Check that there weren't any drivers to unload except in the |
| // versions affected by DERBY-2905. |
| assertEquals("Unexpected result from driver unloading", |
| isAffectedVersion, res.booleanValue()); |
| } |
| } |
| } |
| |
| /** |
| * Clear a static ThreadLocal field in TableDescriptor so that the engine |
| * classes can be garbage collected when they are no longer used. This is |
| * a workaround for DERBY-4895, which affects Derby 10.5 and 10.6. |
| */ |
| private void clearDerby4895ThreadLocal() throws Exception { |
| boolean isAffectedVersion = |
| (UpgradeRun.lessThan(new int[] {10,5,0,0}, version) && |
| UpgradeRun.lessThan(version, new int[] {10,5,3,2})) |
| || |
| (UpgradeRun.lessThan(new int[] {10,6,0,0}, version) && |
| UpgradeRun.lessThan(version, new int[] {10,6,2,3})); |
| |
| if (!isAffectedVersion) { |
| // Nothing to work around in this version. |
| return; |
| } |
| |
| // nothing to do if we are running with a module path. |
| // in that case, the down-rev code runs remotely under |
| // a derby server. the field below no longer exists |
| // in the TableDescriptor class on the trunk. |
| if (JVMInfo.isModuleAware()) { return; } |
| |
| Class td = Class.forName( |
| "org.apache.derby.iapi.sql.dictionary.TableDescriptor", |
| true, loader); |
| clearField(td, "referencedColumnMap", null); |
| } |
| |
| /** |
| * Clear a field that is possibly private or final. |
| * |
| * @param cls the class in which the field lives |
| * @param name the name of the field to clear |
| * @param instance the instance whose field should be cleared, |
| * or null if the field is static |
| */ |
| private static void clearField(Class cls, String name, Object instance) |
| throws Exception { |
| Field f = cls.getDeclaredField(name); |
| f.setAccessible(true); |
| f.set(instance, null); |
| } |
| |
| /** |
| * Get a handle to the ContextService in the old engine if the version |
| * is affected by DERBY-23. |
| * |
| * @return the ContextService, if this version is affected by DERBY-23, |
| * or null otherwise |
| */ |
| private Object getDerby23ContextService() throws Exception { |
| if (loader != null && |
| UpgradeRun.lessThan(version, new int[] {10,2,1,6})) { |
| Class cls = Class.forName( |
| "org.apache.derby.iapi.services.context.ContextService", |
| true, loader); |
| Field f = cls.getDeclaredField("factory"); |
| f.setAccessible(true); |
| return f.get(null); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Clear some fields in ContextService to allow the engine to be garbage |
| * collected. This is a workaround for DERBY-23. |
| * |
| * @param contextService the context service for an engine that has been |
| * shut down, or null if this version of the engine doesn't suffer from |
| * DERBY-23 |
| */ |
| private void clearDerby23ThreadLocals(Object contextService) |
| throws Exception { |
| if (contextService != null) { |
| Class cls = contextService.getClass(); |
| |
| // DERBY-5343: Ideally, we'd just set the two fields to null |
| // like this: |
| // |
| // clearField(cls, "threadContextList", contextService); |
| // clearField(cls, "allContexts", contextService); |
| // |
| // However, the fields are final in the versions that suffer from |
| // DERBY-23, and Java versions prior to Java 5 don't allow us to |
| // modify final fields. So let's take a different approach to make |
| // it work on Java 1.4.2 and Foundation Profile 1.1 as well. |
| |
| // The field threadContextList is a ThreadLocal. Clear it in the |
| // current thread. Assuming all other threads that have accessed |
| // the database engine (background threads and any helper threads |
| // started by the test cases) are stopped and made eligible for |
| // garbage collection, this should be a sufficient replacement for |
| // setting the field to null. |
| Field tclField = cls.getDeclaredField("threadContextList"); |
| tclField.setAccessible(true); |
| ThreadLocal<?> tcl = (ThreadLocal) tclField.get(contextService); |
| tcl.set(null); |
| |
| // The field allContexts is a HashSet. Calling clear() should be |
| // equivalent to setting it to null in terms of making its elements |
| // eligible for garbage collection. |
| Field acField = cls.getDeclaredField("allContexts"); |
| acField.setAccessible(true); |
| Set ac = (Set) acField.get(contextService); |
| ac.clear(); |
| } |
| } |
| } |