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 }