View Javadoc
1   /**
2    * Copyright 2005-2014 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 org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang.time.DurationFormatUtils;
20  import org.apache.commons.lang.time.StopWatch;
21  import org.apache.log4j.Logger;
22  import org.junit.Assert;
23  import org.kuali.rice.core.api.config.property.ConfigContext;
24  import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
25  import org.springframework.dao.DataAccessException;
26  import org.springframework.jdbc.core.ConnectionCallback;
27  import org.springframework.jdbc.core.JdbcTemplate;
28  import org.springframework.jdbc.core.StatementCallback;
29  import org.springframework.transaction.PlatformTransactionManager;
30  import org.springframework.transaction.TransactionStatus;
31  import org.springframework.transaction.support.TransactionCallback;
32  import org.springframework.transaction.support.TransactionTemplate;
33  
34  import javax.sql.DataSource;
35  import java.sql.Connection;
36  import java.sql.DatabaseMetaData;
37  import java.sql.ResultSet;
38  import java.sql.SQLException;
39  import java.sql.Statement;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Map;
44  
45  /**
46   * Lifecycle class to clean up the database for use in testing.
47   * This lifecycle will not be run (even if it is listed in the lifecycles list)
48   * if the 'use.use.clearDatabaseLifecycle' configuration property is defined, and is
49   * not 'true'.  If the property is omitted the lifecycle runs as normal.
50   * 
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   * @since 0.9
53   *
54   */
55  public class ClearDatabaseLifecycle extends BaseLifecycle {
56  
57      protected static final Logger LOG = Logger.getLogger(ClearDatabaseLifecycle.class);
58  
59      private List<String> tablesToClear = new ArrayList<String>();
60      private List<String> tablesNotToClear = new ArrayList<String>();
61  
62      public ClearDatabaseLifecycle() {
63          addStandardTables();
64      }
65  
66      public ClearDatabaseLifecycle(List<String> tablesToClear, List<String> tablesNotToClear) {
67          this.tablesToClear = tablesToClear;
68          this.tablesNotToClear = tablesNotToClear;
69          addStandardTables();
70      }
71  
72      protected void addStandardTables() {
73          tablesNotToClear.add("BIN.*");
74          tablesNotToClear.add(".*_S");
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 Connection connection) throws SQLException {
93          Assert.assertNotNull("Connection could not be located.", connection);
94          ResultSet resultSet = null;
95          try {
96              resultSet = connection.getMetaData().getTables(null, connection.getMetaData().getUserName().toUpperCase(), TEST_TABLE_NAME, null);
97              return new Boolean(resultSet.next());
98          } finally {
99              if (resultSet != null) {
100                 resultSet.close();
101             }
102         }
103     }
104 
105     protected void verifyTestEnvironment(final DataSource dataSource) {
106         new JdbcTemplate(dataSource).execute(new ConnectionCallback<Object>() {
107             public Object doInConnection(final Connection connection) throws SQLException {
108                 String dbUrl = connection.getMetaData().getURL();
109                 Assert.assertTrue("No table named '" + TEST_TABLE_NAME + "' was found in the configured database.  " +
110                         dbUrl + "  You are attempting to run tests against a non-test database!!!", isTestTableInSchema(connection));
111                 return null;
112             }
113         });
114     }
115 
116     protected void clearTables(final PlatformTransactionManager transactionManager, final DataSource dataSource) {
117         Assert.assertNotNull("DataSource could not be located.", dataSource);
118         try {
119             StopWatch s = new StopWatch();
120             s.start();
121             new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
122                 public Object doInTransaction(final TransactionStatus status) {
123                     verifyTestEnvironment(dataSource);
124                     return new JdbcTemplate(dataSource).execute(new StatementCallback() {
125                         public Object doInStatement(Statement statement) throws SQLException {
126                             String schemaName = statement.getConnection().getMetaData().getUserName().toUpperCase();
127                             LOG.info("Clearing tables for schema " + schemaName);
128                             if (StringUtils.isBlank(schemaName)) {
129                                 Assert.fail("Empty schema name given");
130                             }
131                             final List<String> reEnableConstraints = new ArrayList<String>();
132                             DatabaseMetaData metaData = statement.getConnection().getMetaData();
133                             Map<String, List<String[]>> exportedKeys = indexExportedKeys(metaData, schemaName);
134                             final ResultSet resultSet = metaData.getTables(null, schemaName, null, new String[] { "TABLE" });
135                             final StringBuilder logStatements = new StringBuilder();
136                             while (resultSet.next()) {
137                                 String tableName = resultSet.getString("TABLE_NAME");
138                                 if (shouldTableBeCleared(tableName)) {
139                                     if (!isUsingDerby(metaData) && isUsingOracle(metaData)) {
140                                     	List<String[]> exportedKeyNames = exportedKeys.get(tableName);
141                                     	if (exportedKeyNames != null) {
142                                     		for (String[] exportedKeyName : exportedKeyNames) {
143                                     			final String fkName = exportedKeyName[0];
144                                     			final String fkTableName = exportedKeyName[1];
145                                     			final String disableConstraint = "ALTER TABLE " + fkTableName + " DISABLE CONSTRAINT " + fkName;
146                                     			logStatements.append("Disabling constraints using statement ->" + disableConstraint + "<-\n");
147                                     			statement.addBatch(disableConstraint);
148                                     			reEnableConstraints.add("ALTER TABLE " + fkTableName + " ENABLE CONSTRAINT " + fkName);
149                                     		}
150                                     	}
151                                     } else if (isUsingMySQL(metaData)) {
152                                     	statement.addBatch("SET FOREIGN_KEY_CHECKS = 0");
153                                     }
154                                     String deleteStatement = "DELETE FROM " + tableName;
155                                     logStatements.append("Clearing contents using statement ->" + deleteStatement + "<-\n");
156                                     statement.addBatch(deleteStatement);
157                                 }
158                             }
159                             for (final String constraint : reEnableConstraints) {
160                                 logStatements.append("Enabling constraints using statement ->" + constraint + "<-\n");
161                                 statement.addBatch(constraint);
162                             }
163                             if (isUsingMySQL(metaData)) {
164                             	statement.addBatch("SET FOREIGN_KEY_CHECKS = 1");
165                             }
166                             LOG.info(logStatements);
167                             
168                             int[] results = statement.executeBatch();
169                             for (int index = 0; index < results.length; index++) {
170                             	if (results[index] == Statement.EXECUTE_FAILED) {
171                     				Assert.fail("Execution of database clear statement failed.");
172                             	}
173                             	
174                             }
175                             resultSet.close();
176                             LOG.info("Tables successfully cleared for schema " + schemaName);
177                             return null;
178                         }
179                     });
180                 }
181             });
182             s.stop();
183             LOG.info("Time to clear tables: " + DurationFormatUtils.formatDurationHMS(s.getTime()));
184         } catch (Exception e) {
185             LOG.error(e);
186             throw new RuntimeException(e);
187         }
188     }
189     
190     protected Map<String, List<String[]>> indexExportedKeys(DatabaseMetaData metaData, String schemaName) throws SQLException {
191     	Map<String, List<String[]>> exportedKeys = new HashMap<String, List<String[]>>();
192         if (!isUsingDerby(metaData) && isUsingOracle(metaData)) {
193         	ResultSet keyResultSet = metaData.getExportedKeys(null, schemaName, null);
194         	while (keyResultSet.next()) {
195         		String tableName = keyResultSet.getString("PKTABLE_NAME");
196         		if (shouldTableBeCleared(tableName)) {
197         			List<String[]> exportedKeyNames = exportedKeys.get(tableName);
198         			if (exportedKeyNames == null) {
199         				exportedKeyNames = new ArrayList<String[]>();
200         				exportedKeys.put(tableName, exportedKeyNames);
201         			}
202         			final String fkName = keyResultSet.getString("FK_NAME");
203         			final String fkTableName = keyResultSet.getString("FKTABLE_NAME");
204         			exportedKeyNames.add(new String[] { fkName, fkTableName });
205         		}
206         	}
207         	keyResultSet.close();
208         }
209         return exportedKeys;        
210     }
211 
212     private boolean shouldTableBeCleared(String tableName) {
213         if (getTablesNotToClear() != null && !getTablesNotToClear().isEmpty()) {
214             for (String tableNotToClear : getTablesNotToClear()) {
215                 if (tableName.toUpperCase().matches(tableNotToClear.toUpperCase())) {
216                     return false;
217                 }
218             }
219         }
220         if (getTablesToClear() != null && !getTablesToClear().isEmpty()) {
221             for (String tableToClear : getTablesToClear()) {
222                 if (tableName.toUpperCase().matches(tableToClear.toUpperCase())) {
223                     return true;
224                 }
225             }
226             return false;
227         }
228         
229         return true;
230     }
231 
232     private boolean isUsingDerby(DatabaseMetaData metaData) throws SQLException {
233         return metaData.getDriverName().toLowerCase().contains("derby");
234     }
235 
236     private boolean isUsingOracle(DatabaseMetaData metaData) throws SQLException {
237         return metaData.getDriverName().toLowerCase().contains("oracle");
238     }
239 
240     private boolean isUsingMySQL(DatabaseMetaData metaData) throws SQLException {
241         return metaData.getDriverName().toLowerCase().contains("mysql");
242     }
243 
244     
245     public List<String> getTablesToClear() {
246         return this.tablesToClear;
247     }
248 
249     public List<String> getTablesNotToClear() {
250         return this.tablesNotToClear;
251     }
252 }