1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.test;
17
18 import java.sql.Connection;
19 import java.sql.DatabaseMetaData;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.sql.Statement;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import javax.sql.DataSource;
29
30 import junit.framework.Assert;
31
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.commons.lang.time.DurationFormatUtils;
34 import org.apache.commons.lang.time.StopWatch;
35 import org.apache.log4j.Logger;
36 import org.kuali.rice.core.api.config.property.ConfigContext;
37 import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
38 import org.springframework.jdbc.core.ConnectionCallback;
39 import org.springframework.jdbc.core.JdbcTemplate;
40 import org.springframework.jdbc.core.StatementCallback;
41 import org.springframework.transaction.PlatformTransactionManager;
42 import org.springframework.transaction.TransactionStatus;
43 import org.springframework.transaction.support.TransactionCallback;
44 import org.springframework.transaction.support.TransactionTemplate;
45
46
47
48
49
50
51
52
53
54
55
56 public class ClearDatabaseLifecycle extends BaseLifecycle {
57
58 protected static final Logger LOG = Logger.getLogger(ClearDatabaseLifecycle.class);
59
60 private List<String> tablesToClear = new ArrayList<String>();
61 private List<String> tablesNotToClear = new ArrayList<String>();
62
63 public ClearDatabaseLifecycle() {
64 addStandardTables();
65 }
66
67 public ClearDatabaseLifecycle(List<String> tablesToClear, List<String> tablesNotToClear) {
68 this.tablesToClear = tablesToClear;
69 this.tablesNotToClear = tablesNotToClear;
70 addStandardTables();
71 }
72
73 protected void addStandardTables() {
74 tablesNotToClear.add("BIN.*");
75 }
76
77 public static final String TEST_TABLE_NAME = "EN_UNITTEST_T";
78
79 public void start() throws Exception {
80 String useClearDatabaseLifecycle = ConfigContext.getCurrentContextConfig().getProperty("use.clearDatabaseLifecycle");
81
82 if (useClearDatabaseLifecycle != null && !Boolean.valueOf(useClearDatabaseLifecycle)) {
83 LOG.debug("Skipping ClearDatabaseLifecycle due to property: use.clearDatabaseLifecycle=" + useClearDatabaseLifecycle);
84 return;
85 }
86
87 final DataSource dataSource = TestHarnessServiceLocator.getDataSource();
88 clearTables(TestHarnessServiceLocator.getJtaTransactionManager(), dataSource);
89 super.start();
90 }
91
92 protected Boolean isTestTableInSchema(final DataSource dataSource) {
93 Assert.assertNotNull("DataSource could not be located.", dataSource);
94 try {
95 Connection connection = dataSource.getConnection();
96 connection.close();
97 } catch (Exception e) {
98 throw new RuntimeException(e);
99 }
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 }