View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * Lifecycle class to clean up the database for use in testing.
48   * This lifecycle will not be run (even if it is listed in the lifecycles list)
49   * if the 'use.use.clearDatabaseLifecycle' configuration property is defined, and is
50   * not 'true'.  If the property is omitted the lifecycle runs as normal.
51   * 
52   * @author Kuali Rice Team (rice.collab@kuali.org)
53   * @since 0.9
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 }