001 /* 002 * Copyright 2007 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.test; 017 018 import java.sql.Connection; 019 import java.sql.DatabaseMetaData; 020 import java.sql.ResultSet; 021 import java.sql.SQLException; 022 import java.sql.Statement; 023 import java.util.ArrayList; 024 import java.util.HashMap; 025 import java.util.List; 026 import java.util.Map; 027 028 import javax.sql.DataSource; 029 030 import junit.framework.Assert; 031 032 import org.apache.commons.lang.StringUtils; 033 import org.apache.commons.lang.time.DurationFormatUtils; 034 import org.apache.commons.lang.time.StopWatch; 035 import org.apache.log4j.Logger; 036 import org.kuali.rice.core.api.config.property.ConfigContext; 037 import org.kuali.rice.core.api.lifecycle.BaseLifecycle; 038 import org.springframework.jdbc.core.ConnectionCallback; 039 import org.springframework.jdbc.core.JdbcTemplate; 040 import org.springframework.jdbc.core.StatementCallback; 041 import org.springframework.transaction.PlatformTransactionManager; 042 import org.springframework.transaction.TransactionStatus; 043 import org.springframework.transaction.support.TransactionCallback; 044 import org.springframework.transaction.support.TransactionTemplate; 045 046 /** 047 * Lifecycle class to clean up the database for use in testing. 048 * This lifecycle will not be run (even if it is listed in the lifecycles list) 049 * if the 'use.use.clearDatabaseLifecycle' configuration property is defined, and is 050 * not 'true'. If the property is omitted the lifecycle runs as normal. 051 * 052 * @author Kuali Rice Team (rice.collab@kuali.org) 053 * @since 0.9 054 * 055 */ 056 public class ClearDatabaseLifecycle extends BaseLifecycle { 057 058 protected static final Logger LOG = Logger.getLogger(ClearDatabaseLifecycle.class); 059 060 private List<String> tablesToClear = new ArrayList<String>(); 061 private List<String> tablesNotToClear = new ArrayList<String>(); 062 063 public ClearDatabaseLifecycle() { 064 addStandardTables(); 065 } 066 067 public ClearDatabaseLifecycle(List<String> tablesToClear, List<String> tablesNotToClear) { 068 this.tablesToClear = tablesToClear; 069 this.tablesNotToClear = tablesNotToClear; 070 addStandardTables(); 071 } 072 073 protected void addStandardTables() { 074 tablesNotToClear.add("BIN.*"); 075 } 076 077 public static final String TEST_TABLE_NAME = "EN_UNITTEST_T"; 078 079 public void start() throws Exception { 080 String useClearDatabaseLifecycle = ConfigContext.getCurrentContextConfig().getProperty("use.clearDatabaseLifecycle"); 081 082 if (useClearDatabaseLifecycle != null && !Boolean.valueOf(useClearDatabaseLifecycle)) { 083 LOG.debug("Skipping ClearDatabaseLifecycle due to property: use.clearDatabaseLifecycle=" + useClearDatabaseLifecycle); 084 return; 085 } 086 087 final DataSource dataSource = TestHarnessServiceLocator.getDataSource(); 088 clearTables(TestHarnessServiceLocator.getJtaTransactionManager(), dataSource); 089 super.start(); 090 } 091 092 protected Boolean isTestTableInSchema(final DataSource dataSource) { 093 Assert.assertNotNull("DataSource could not be located.", dataSource); 094 try { 095 Connection connection = dataSource.getConnection(); 096 connection.close(); 097 } catch (Exception e) { 098 throw new RuntimeException(e); 099 } 100 return (Boolean) new JdbcTemplate(dataSource).execute(new ConnectionCallback() { 101 public Object doInConnection(final Connection connection) throws SQLException { 102 final ResultSet resultSet = connection.getMetaData().getTables(null, connection.getMetaData().getUserName().toUpperCase(), TEST_TABLE_NAME, null); 103 return new Boolean(resultSet.next()); 104 } 105 }); 106 } 107 108 protected void verifyTestEnvironment(final DataSource dataSource) { 109 Assert.assertTrue("No table named '" + TEST_TABLE_NAME + "' was found in the configured database. " + "You are attempting to run tests against a non-test database!!!", isTestTableInSchema(dataSource)); 110 } 111 112 protected void clearTables(final PlatformTransactionManager transactionManager, final DataSource dataSource) { 113 Assert.assertNotNull("DataSource could not be located.", dataSource); 114 try { 115 StopWatch s = new StopWatch(); 116 s.start(); 117 new TransactionTemplate(transactionManager).execute(new TransactionCallback() { 118 public Object doInTransaction(final TransactionStatus status) { 119 verifyTestEnvironment(dataSource); 120 return new JdbcTemplate(dataSource).execute(new StatementCallback() { 121 public Object doInStatement(Statement statement) throws SQLException { 122 String schemaName = statement.getConnection().getMetaData().getUserName().toUpperCase(); 123 LOG.info("Clearing tables for schema " + schemaName); 124 if (StringUtils.isBlank(schemaName)) { 125 Assert.fail("Empty schema name given"); 126 } 127 final List<String> reEnableConstraints = new ArrayList<String>(); 128 DatabaseMetaData metaData = statement.getConnection().getMetaData(); 129 Map<String, List<String[]>> exportedKeys = indexExportedKeys(metaData, schemaName); 130 final ResultSet resultSet = metaData.getTables(null, schemaName, null, new String[] { "TABLE" }); 131 final StringBuilder logStatements = new StringBuilder(); 132 while (resultSet.next()) { 133 String tableName = resultSet.getString("TABLE_NAME"); 134 if (shouldTableBeCleared(tableName)) { 135 if (!isUsingDerby(metaData) && isUsingOracle(metaData)) { 136 List<String[]> exportedKeyNames = exportedKeys.get(tableName); 137 if (exportedKeyNames != null) { 138 for (String[] exportedKeyName : exportedKeyNames) { 139 final String fkName = exportedKeyName[0]; 140 final String fkTableName = exportedKeyName[1]; 141 final String disableConstraint = "ALTER TABLE " + fkTableName + " DISABLE CONSTRAINT " + fkName; 142 logStatements.append("Disabling constraints using statement ->" + disableConstraint + "<-\n"); 143 statement.addBatch(disableConstraint); 144 reEnableConstraints.add("ALTER TABLE " + fkTableName + " ENABLE CONSTRAINT " + fkName); 145 } 146 } 147 } else if (isUsingMySQL(metaData)) { 148 statement.addBatch("SET FOREIGN_KEY_CHECKS = 0"); 149 } 150 String deleteStatement = "DELETE FROM " + tableName; 151 logStatements.append("Clearing contents using statement ->" + deleteStatement + "<-\n"); 152 statement.addBatch(deleteStatement); 153 } 154 } 155 for (final String constraint : reEnableConstraints) { 156 logStatements.append("Enabling constraints using statement ->" + constraint + "<-\n"); 157 statement.addBatch(constraint); 158 } 159 if (isUsingMySQL(metaData)) { 160 statement.addBatch("SET FOREIGN_KEY_CHECKS = 1"); 161 } 162 LOG.info(logStatements); 163 164 int[] results = statement.executeBatch(); 165 for (int index = 0; index < results.length; index++) { 166 if (results[index] == Statement.EXECUTE_FAILED) { 167 Assert.fail("Execution of database clear statement failed."); 168 } 169 170 } 171 resultSet.close(); 172 LOG.info("Tables successfully cleared for schema " + schemaName); 173 return null; 174 } 175 }); 176 } 177 }); 178 s.stop(); 179 LOG.info("Time to clear tables: " + DurationFormatUtils.formatDurationHMS(s.getTime())); 180 } catch (Exception e) { 181 LOG.error(e); 182 throw new RuntimeException(e); 183 } 184 } 185 186 protected Map<String, List<String[]>> indexExportedKeys(DatabaseMetaData metaData, String schemaName) throws SQLException { 187 Map<String, List<String[]>> exportedKeys = new HashMap<String, List<String[]>>(); 188 if (!isUsingDerby(metaData) && isUsingOracle(metaData)) { 189 ResultSet keyResultSet = metaData.getExportedKeys(null, schemaName, null); 190 while (keyResultSet.next()) { 191 String tableName = keyResultSet.getString("PKTABLE_NAME"); 192 if (shouldTableBeCleared(tableName)) { 193 List<String[]> exportedKeyNames = exportedKeys.get(tableName); 194 if (exportedKeyNames == null) { 195 exportedKeyNames = new ArrayList<String[]>(); 196 exportedKeys.put(tableName, exportedKeyNames); 197 } 198 final String fkName = keyResultSet.getString("FK_NAME"); 199 final String fkTableName = keyResultSet.getString("FKTABLE_NAME"); 200 exportedKeyNames.add(new String[] { fkName, fkTableName }); 201 } 202 } 203 keyResultSet.close(); 204 } 205 return exportedKeys; 206 } 207 208 private boolean shouldTableBeCleared(String tableName) { 209 if (getTablesToClear() != null && !getTablesToClear().isEmpty()) { 210 for (String tableToClear : getTablesToClear()) { 211 if (tableName.toUpperCase().matches(tableToClear.toUpperCase())) { 212 return true; 213 } 214 } 215 return false; 216 } 217 if (getTablesNotToClear() != null && !getTablesNotToClear().isEmpty()) { 218 for (String tableNotToClear : getTablesNotToClear()) { 219 if (tableName.toUpperCase().matches(tableNotToClear.toUpperCase())) { 220 return false; 221 } 222 } 223 } 224 return true; 225 } 226 227 private boolean isUsingDerby(DatabaseMetaData metaData) throws SQLException { 228 return metaData.getDriverName().toLowerCase().contains("derby"); 229 } 230 231 private boolean isUsingOracle(DatabaseMetaData metaData) throws SQLException { 232 return metaData.getDriverName().toLowerCase().contains("oracle"); 233 } 234 235 private boolean isUsingMySQL(DatabaseMetaData metaData) throws SQLException { 236 return metaData.getDriverName().toLowerCase().contains("mysql"); 237 } 238 239 240 public List<String> getTablesToClear() { 241 return this.tablesToClear; 242 } 243 244 public List<String> getTablesNotToClear() { 245 return this.tablesNotToClear; 246 } 247 }