001: /*
002: * Copyright 2006-2007, Unitils.org
003: *
004: * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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.unitils.dbmaintainer.clean.impl;
017:
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import org.unitils.core.UnitilsException;
021: import org.unitils.core.dbsupport.DbSupport;
022: import org.unitils.dbmaintainer.clean.DBClearer;
023: import org.unitils.dbmaintainer.util.BaseDatabaseTask;
024: import static org.unitils.util.PropertyUtils.getStringList;
025:
026: import java.util.*;
027:
028: /**
029: * Implementation of {@link DBClearer}. This implementation individually drops every table, view, constraint, trigger
030: * and sequence in the database. A list of tables, views, ... that should be preserverd can be specified using the
031: * property {@link #PROPKEY_PRESERVE_TABLES}. <p/> NOTE: FK constraints give problems in MySQL and Derby The cascade in
032: * drop table A cascade; does not work in MySQL-5.0 The DBMaintainer will first remove all constraints before calling
033: * the db clearer
034: *
035: * @author Tim Ducheyne
036: * @author Filip Neven
037: */
038: public class DefaultDBClearer extends BaseDatabaseTask implements
039: DBClearer {
040:
041: /**
042: * The key of the property that specifies of which schemas nothing should be dropped
043: */
044: public static final String PROPKEY_PRESERVE_SCHEMAS = "dbMaintainer.preserve.schemas";
045:
046: /**
047: * The key of the property that specifies which tables should not be dropped
048: */
049: public static final String PROPKEY_PRESERVE_TABLES = "dbMaintainer.preserve.tables";
050:
051: /**
052: * The key of the property that specifies which views should not be dropped
053: */
054: public static final String PROPKEY_PRESERVE_VIEWS = "dbMaintainer.preserve.views";
055:
056: /**
057: * The key of the property that specifies which synonyms should not be dropped
058: */
059: public static final String PROPKEY_PRESERVE_SYNONYMS = "dbMaintainer.preserve.synonyms";
060:
061: /**
062: * The key of the property that specifies which sequences should not be dropped
063: */
064: public static final String PROPKEY_PRESERVE_SEQUENCES = "dbMaintainer.preserve.sequences";
065:
066: /**
067: * The key of the property that specifies the name of the datase table in which the
068: * DB version is stored. This table should not be deleted
069: */
070: public static final String PROPKEY_VERSION_TABLE_NAME = "dbMaintainer.dbVersionSource.tableName";
071:
072: /* The logger instance for this class */
073: private static Log logger = LogFactory
074: .getLog(DefaultDBClearer.class);
075:
076: /**
077: * Names of schemas that should left untouched.
078: */
079: protected Set<String> schemasToPreserve;
080:
081: /**
082: * Names of tables that should not be dropped per schema.
083: */
084: protected Map<String, Set<String>> tablesToPreserve;
085:
086: /**
087: * Names of views that should not be dropped per schema.
088: */
089: protected Map<String, Set<String>> viewsToPreserve;
090:
091: /**
092: * Names of synonyms that should not be dropped per schema.
093: */
094: protected Map<String, Set<String>> synonymsToPreserve;
095:
096: /**
097: * Names of sequences that should not be dropped per schema.
098: */
099: protected Map<String, Set<String>> sequencesToPreserve;
100:
101: /**
102: * Initializes the the DBClearer. The list of database items that should be preserved is retrieved from the given
103: * <code>Configuration</code> object.
104: *
105: * @param configuration the config, not null
106: */
107: @Override
108: protected void doInit(Properties configuration) {
109: schemasToPreserve = getSchemasToPreserve(configuration);
110: tablesToPreserve = getTablesToPreserve(configuration); // also adds db version table
111: viewsToPreserve = getViewsToPreserve(configuration);
112: sequencesToPreserve = getSequencesToPreserve(configuration);
113: synonymsToPreserve = getSynonymsToPreserve(configuration);
114: }
115:
116: /**
117: * Clears the database schemas. This means, all the tables, views, constraints, triggers and sequences are dropped,
118: * so that the database schema is empty. The database items that are configured as items to preserve, are left
119: * untouched.
120: */
121: public void clearSchemas() {
122: for (DbSupport dbSupport : dbSupports) {
123: // check whether schema needs to be preserved
124: if (schemasToPreserve.contains(dbSupport.getSchemaName())) {
125: continue;
126: }
127: logger.info("Clearing (dropping) database schema "
128: + dbSupport.getSchemaName());
129: dropSynonyms(dbSupport);
130: dropViews(dbSupport);
131: dropSequences(dbSupport);
132: dropTables(dbSupport);
133: }
134: }
135:
136: /**
137: * Drops all tables.
138: *
139: * @param dbSupport The database support, not null
140: */
141: protected void dropTables(DbSupport dbSupport) {
142: Set<String> tableNames = dbSupport.getTableNames();
143: Set<String> schemaTablesToPreserve = tablesToPreserve
144: .get(dbSupport.getSchemaName());
145: for (String tableName : tableNames) {
146: // check whether table needs to be preserved
147: if (schemaTablesToPreserve != null
148: && schemaTablesToPreserve.contains(tableName)) {
149: continue;
150: }
151: logger.debug("Dropping table " + tableName
152: + " in database schema "
153: + dbSupport.getSchemaName());
154: dbSupport.dropTable(tableName);
155: }
156: }
157:
158: /**
159: * Drops all views.
160: *
161: * @param dbSupport The database support, not null
162: */
163: protected void dropViews(DbSupport dbSupport) {
164: Set<String> viewNames = dbSupport.getViewNames();
165: Set<String> schemaViewsToPreserve = viewsToPreserve
166: .get(dbSupport.getSchemaName());
167: for (String viewName : viewNames) {
168: // check whether view needs to be preserved
169: if (schemaViewsToPreserve != null
170: && schemaViewsToPreserve.contains(viewName)) {
171: continue;
172: }
173: logger.debug("Dropping view " + viewName
174: + " in database schema "
175: + dbSupport.getSchemaName());
176: dbSupport.dropView(viewName);
177: }
178: }
179:
180: /**
181: * Drops all synonyms
182: *
183: * @param dbSupport The database support, not null
184: */
185: protected void dropSynonyms(DbSupport dbSupport) {
186: if (!dbSupport.supportsSynonyms()) {
187: return;
188: }
189: Set<String> synonymNames = dbSupport.getSynonymNames();
190: Set<String> schemaSynonymsToPreserve = synonymsToPreserve
191: .get(dbSupport.getSchemaName());
192: for (String synonymName : synonymNames) {
193: // check whether table needs to be preserved
194: if (schemaSynonymsToPreserve != null
195: && schemaSynonymsToPreserve.contains(synonymName)) {
196: continue;
197: }
198: logger.debug("Dropping synonym " + synonymName
199: + " in database schema "
200: + dbSupport.getSchemaName());
201: dbSupport.dropSynonym(synonymName);
202: }
203: }
204:
205: /**
206: * Drops all sequences
207: *
208: * @param dbSupport The database support, not null
209: */
210: protected void dropSequences(DbSupport dbSupport) {
211: if (!dbSupport.supportsSequences()) {
212: return;
213: }
214: Set<String> sequenceNames = dbSupport.getSequenceNames();
215: Set<String> schemaSequencesToPreserve = sequencesToPreserve
216: .get(dbSupport.getSchemaName());
217: for (String sequenceName : sequenceNames) {
218: // check whether sequence needs to be preserved
219: if (schemaSequencesToPreserve != null
220: && schemaSequencesToPreserve.contains(sequenceName)) {
221: continue;
222: }
223: logger.debug("Dropping sequence " + sequenceName
224: + " in database schema "
225: + dbSupport.getSchemaName());
226: dbSupport.dropSequence(sequenceName);
227: }
228: }
229:
230: /**
231: * Gets the list of all schemas to preserve. The case is corrected if necesary. Quoting a schema name makes it case
232: * sensitive.
233: * <p/>
234: * If a schema name is not defined in the unitils configuration, a UnitilsException is thrown.
235: *
236: * @param configuration The unitils configuration, not null
237: * @return The schemas to preserve, not null
238: */
239: protected Set<String> getSchemasToPreserve(Properties configuration) {
240: Set<String> result = new HashSet<String>();
241:
242: List<String> schemasToPreserve = getStringList(
243: PROPKEY_PRESERVE_SCHEMAS, configuration);
244: for (String schemaToPreserve : schemasToPreserve) {
245:
246: boolean found = false;
247: for (DbSupport dbSupport : dbSupports) {
248: // convert to correct case if needed
249: String correctCaseSchemaToPreserve = dbSupport
250: .toCorrectCaseIdentifier(schemaToPreserve);
251: if (dbSupport.getSchemaName().equals(
252: correctCaseSchemaToPreserve)) {
253: found = true;
254: result.add(correctCaseSchemaToPreserve);
255: break;
256: }
257: }
258: if (!found) {
259: throw new UnitilsException(
260: "Schema to preserve does not exist: "
261: + schemaToPreserve
262: + ".\nUnitils cannot determine which schemas need to be preserved. To assure nothing is dropped by mistake, no schemas will be dropped.\nPlease fix the configuration of the "
263: + PROPKEY_PRESERVE_SCHEMAS
264: + " property.");
265: }
266: }
267: return result;
268: }
269:
270: /**
271: * Gets the list of all tables to preserve per schema. The case is corrected if necesary. Quoting a table name makes
272: * it case sensitive. If no schema is specified, the tables will be added to the default schema name set.
273: * <p/>
274: * If a table to preserve does not exist, a UnitilsException is thrown.
275: * <p/>
276: * The db version table is also added as a table to preserve, but will not be checked on existence.
277: *
278: * @param configuration The unitils configuration, not null
279: * @return The tables to preserve per schema, not null
280: */
281: protected Map<String, Set<String>> getTablesToPreserve(
282: Properties configuration) {
283: Map<String, Set<String>> tablesToPreserve = getItemsToPreserve(
284: PROPKEY_PRESERVE_TABLES, configuration);
285: for (Map.Entry<String, Set<String>> entry : tablesToPreserve
286: .entrySet()) {
287: String schemaName = entry.getKey();
288: Set<String> tableNames = getDbSupport(schemaName)
289: .getTableNames();
290:
291: for (String tableToPreserve : entry.getValue()) {
292: if (!tableNames.contains(tableToPreserve)) {
293: throw new UnitilsException(
294: "Table to preserve does not exist: "
295: + tableToPreserve
296: + " in schema: "
297: + schemaName
298: + ".\nUnitils cannot determine which tables need to be preserved. To assure nothing is dropped by mistake, no tables will be dropped.\nPlease fix the configuration of the "
299: + PROPKEY_PRESERVE_TABLES
300: + " property.");
301: }
302: }
303: }
304:
305: // add version table as item to preserve
306: Map<String, Set<String>> dbVersionTablesToPreserve = getItemsToPreserve(
307: PROPKEY_VERSION_TABLE_NAME, configuration);
308: for (Map.Entry<String, Set<String>> entry : dbVersionTablesToPreserve
309: .entrySet()) {
310: String schemaName = entry.getKey();
311: Set<String> dbVersionTableNames = entry.getValue();
312:
313: Set<String> tableNames = tablesToPreserve.get(schemaName);
314: if (tableNames == null) {
315: tablesToPreserve.put(schemaName, dbVersionTableNames);
316: } else {
317: tableNames.addAll(dbVersionTableNames);
318: }
319: }
320: return tablesToPreserve;
321: }
322:
323: /**
324: * Gets the list of all views to preserve per schema. The case is corrected if necesary. Quoting a view name makes
325: * it case sensitive. If no schema is specified, the view will be added to the default schema name set.
326: * <p/>
327: * If a view to preserve does not exist, a UnitilsException is thrown.
328: *
329: * @param configuration The unitils configuration, not null
330: * @return The views to preserve per schema, not null
331: */
332: protected Map<String, Set<String>> getViewsToPreserve(
333: Properties configuration) {
334: Map<String, Set<String>> viewsToPreserve = getItemsToPreserve(
335: PROPKEY_PRESERVE_VIEWS, configuration);
336: for (Map.Entry<String, Set<String>> entry : viewsToPreserve
337: .entrySet()) {
338: String schemaName = entry.getKey();
339: Set<String> viewNames = getDbSupport(schemaName)
340: .getViewNames();
341:
342: for (String viewToPreserve : entry.getValue()) {
343: if (!viewNames.contains(viewToPreserve)) {
344: throw new UnitilsException(
345: "View to preserve does not exist: "
346: + viewToPreserve
347: + " in schema: "
348: + schemaName
349: + ".\nUnitils cannot determine which views need to be preserved. To assure nothing is dropped by mistake, no views will be dropped.\nPlease fix the configuration of the "
350: + PROPKEY_PRESERVE_VIEWS
351: + " property.");
352: }
353: }
354: }
355: return viewsToPreserve;
356: }
357:
358: /**
359: * Gets the list of all sequences to preserve per schema. The case is corrected if necesary. Quoting a sequence name
360: * makes it case sensitive. If no schema is specified, the sequence will be added to the default schema name set.
361: * <p/>
362: * If a sequence to preserve does not exist, a UnitilsException is thrown.
363: *
364: * @param configuration The unitils configuration, not null
365: * @return The sequences to preserve per schema, not null
366: */
367: protected Map<String, Set<String>> getSequencesToPreserve(
368: Properties configuration) {
369: Map<String, Set<String>> sequencesToPreserve = getItemsToPreserve(
370: PROPKEY_PRESERVE_SEQUENCES, configuration);
371: for (Map.Entry<String, Set<String>> entry : sequencesToPreserve
372: .entrySet()) {
373: String schemaName = entry.getKey();
374:
375: DbSupport dbSupport = getDbSupport(schemaName);
376: Set<String> sequenceNames;
377: if (!dbSupport.supportsSequences()) {
378: sequenceNames = new HashSet<String>();
379: } else {
380: sequenceNames = dbSupport.getSequenceNames();
381: }
382: for (String sequenceToPreserve : entry.getValue()) {
383: if (!sequenceNames.contains(sequenceToPreserve)) {
384: throw new UnitilsException(
385: "Sequence to preserve does not exist: "
386: + sequenceToPreserve
387: + " in schema: "
388: + schemaName
389: + ".\nUnitils cannot determine which sequences need to be preserved. To assure nothing is dropped by mistake, no sequences will be dropped.\nPlease fix the configuration of the "
390: + PROPKEY_PRESERVE_SEQUENCES
391: + " property.");
392: }
393: }
394: }
395: return sequencesToPreserve;
396: }
397:
398: /**
399: * Gets the list of all synonym to preserve per schema. The case is corrected if necesary. Quoting a synonym name
400: * makes it case sensitive. If no schema is specified, the synonym will be added to the default schema name set.
401: * <p/>
402: * If a synonym to preserve does not exist, a UnitilsException is thrown.
403: *
404: * @param configuration The unitils configuration, not null
405: * @return The synonym to preserve per schema, not null
406: */
407: protected Map<String, Set<String>> getSynonymsToPreserve(
408: Properties configuration) {
409: Map<String, Set<String>> synonymsToPreserve = getItemsToPreserve(
410: PROPKEY_PRESERVE_SYNONYMS, configuration);
411: for (Map.Entry<String, Set<String>> entry : synonymsToPreserve
412: .entrySet()) {
413: String schemaName = entry.getKey();
414:
415: DbSupport dbSupport = getDbSupport(schemaName);
416: Set<String> synonymNames;
417: if (!dbSupport.supportsSynonyms()) {
418: synonymNames = new HashSet<String>();
419: } else {
420: synonymNames = dbSupport.getSynonymNames();
421: }
422: for (String synonymToPreserve : entry.getValue()) {
423: if (!synonymNames.contains(synonymToPreserve)) {
424: throw new UnitilsException(
425: "Synonym to preserve does not exist: "
426: + synonymToPreserve
427: + " in schema: "
428: + schemaName
429: + ".\nUnitils cannot determine which synonyms need to be preserved. To assure nothing is dropped by mistake, no synonyms will be dropped.\nPlease fix the configuration of the "
430: + PROPKEY_PRESERVE_SYNONYMS
431: + " property.");
432: }
433: }
434: }
435: return synonymsToPreserve;
436: }
437:
438: /**
439: * Gets the list of items to preserve per schema. The case is corrected if necesary. Quoting an identifier makes it
440: * case sensitive. If no schema is specified, the identifiers will be added to the default schema name set.
441: *
442: * @param propertyName The name of the property that defines the items, not null
443: * @param configuration The config, not null
444: * @return The set of items per schema name, not null
445: */
446: protected Map<String, Set<String>> getItemsToPreserve(
447: String propertyName, Properties configuration) {
448: Map<String, Set<String>> result = new HashMap<String, Set<String>>();
449:
450: List<String> itemsToPreserve = getStringList(propertyName,
451: configuration);
452: for (String itemToPreserve : itemsToPreserve) {
453:
454: // parse item string
455: DbSupport dbSupport;
456: int index = itemToPreserve.indexOf('.');
457: if (itemToPreserve.indexOf('.') == -1) {
458: dbSupport = defaultDbSupport;
459: } else {
460: String schemaName = itemToPreserve.substring(0, index);
461: dbSupport = getDbSupport(schemaName);
462: itemToPreserve = itemToPreserve.substring(index + 1);
463: }
464:
465: // convert to correct case if needed
466: String correctCaseItemToPreserve = dbSupport
467: .toCorrectCaseIdentifier(itemToPreserve);
468:
469: // store item per schema
470: Set<String> schemaItems = result.get(dbSupport
471: .getSchemaName());
472: if (schemaItems == null) {
473: schemaItems = new HashSet<String>();
474: result.put(dbSupport.getSchemaName(), schemaItems);
475: }
476: schemaItems.add(correctCaseItemToPreserve);
477: }
478: return result;
479: }
480: }
|