Project

General

Profile

Download (20.9 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2009 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.database.update;
10

    
11
import java.util.ArrayList;
12
import java.util.Arrays;
13
import java.util.List;
14

    
15
import org.apache.commons.lang.StringUtils;
16
import org.apache.log4j.Logger;
17

    
18
import eu.etaxonomy.cdm.common.CdmUtils;
19
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
20
import eu.etaxonomy.cdm.database.DatabaseTypeEnum;
21
import eu.etaxonomy.cdm.database.ICdmDataSource;
22

    
23
/**
24
 * @author a.mueller
25
 * @since 16.09.2010
26
 *
27
 */
28
public class TableCreator extends AuditedSchemaUpdaterStepBase {
29
	private static final Logger logger = Logger.getLogger(TableCreator.class);
30

    
31
	private static final boolean IS_LIST = true;
32
	private static final boolean IS_1_TO_M = true;
33
	private static final boolean IS_M_TO_M = false;
34

    
35

    
36
	private final List<String> columnNames;
37
	private final List<String> columnTypes;
38
	private final List<Object> defaultValues;
39
	private final List<Boolean> isNotNull;
40
	private final List<String> referencedTables;
41
	private final boolean includeCdmBaseAttributes;
42
	private final boolean includeIdentifiableEntity;
43
	private final boolean includeAnnotatableEntity;
44
	private boolean includeEventBase;
45
	private final boolean excludeVersionableAttributes;
46
	protected List<ColumnAdder> columnAdders = new ArrayList<>();
47
	protected List<ISchemaUpdaterStep> mnTablesStepList = new ArrayList<>();
48
	private String primaryKeyParams;
49
	private String primaryKeyParams_AUD;
50
	private String uniqueParams;
51
	private String uniqueParams_AUD;
52

    
53

    
54
	public static final TableCreator NewInstance(String stepName, String tableName, List<String> columnNames, List<String> columnTypes, boolean includeAudTable, boolean includeCdmBaseAttributes){
55
		return new TableCreator(stepName, tableName, columnNames, columnTypes, null, null, null, includeAudTable, includeCdmBaseAttributes, false, false, false);
56
	}
57

    
58
	public static final TableCreator NewInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable, boolean includeCdmBaseAttributes){
59
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, includeCdmBaseAttributes, false, false, false);
60
	}
61

    
62
	public static final TableCreator NewNonVersionableInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables){
63
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), false, true, false, false, true);
64
	}
65

    
66
	public static final TableCreator NewVersionableInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
67
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, false, false, false);
68
	}
69

    
70
	public static final TableCreator NewAnnotatableInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
71
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, false, false);
72
	}
73

    
74
	public static final TableCreator NewEventInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
75
		TableCreator result = new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, false, false);
76
		result.includeEventBase = true;
77
		return result;
78
	}
79

    
80
	public static final TableCreator NewIdentifiableInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
81
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, true, false);
82
	}
83

    
84
	protected TableCreator(String stepName, String tableName, List<String> columnNames, List<String> columnTypes, List<Object> defaultValues, List<Boolean> isNotNull, List<String> referencedTables,
85
			boolean includeAudTable, boolean includeCdmBaseAttributes, boolean includeAnnotatableEntity, boolean includeIdentifiableEntity, boolean excludeVersionableAttributes) {
86
		super(stepName, tableName, includeAudTable);
87
		this.columnNames = columnNames;
88
		this.columnTypes = columnTypes;
89
		this.defaultValues = defaultValues;
90
		this.isNotNull = isNotNull;
91
		this.referencedTables = referencedTables;
92
		this.includeCdmBaseAttributes = includeCdmBaseAttributes;
93
		this.includeAnnotatableEntity = includeAnnotatableEntity;
94
		this.includeIdentifiableEntity = includeIdentifiableEntity;
95
		this.excludeVersionableAttributes = excludeVersionableAttributes;
96
		makeColumnAdders();
97
		makeMnTables(mnTablesStepList, this.tableName, this.includeAnnotatableEntity, this.includeIdentifiableEntity);
98
	}
99

    
100

    
101
	@Override
102
	public List<ISchemaUpdaterStep> getInnerSteps() {
103
		return mnTablesStepList;
104
	}
105

    
106
	/**
107
	 * Fills the {@link #columnAdders} list.
108
	 */
109
	private void makeColumnAdders() {
110
		if (columnNames.size() != columnTypes.size()){
111
			throw new RuntimeException ("ColumnNames and columnTypes must be of same size. Step: " + getStepName());
112
		}
113

    
114
		try {
115
			for (int i = 0; i < columnNames.size(); i++){
116
				boolean isNotNull = this.isNotNull == null ? false : this.isNotNull.get(i);
117
				if ("integer".equals(columnTypes.get(i)) || "int".equals(columnTypes.get(i))){
118
					String referencedTable = (this.referencedTables == null) ?  null : this.referencedTables.get(i);
119
					ColumnAdder adder = ColumnAdder.NewIntegerInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull, referencedTable);
120
					this.columnAdders.add(adder);
121
				}else if ("boolean".equals(columnTypes.get(i)) || "bit".equals(columnTypes.get(i))){
122
					String defaultValue = this.defaultValues == null ? null : this.defaultValues.get(i).toString();
123
					ColumnAdder adder = ColumnAdder.NewBooleanInstance(getStepName(), this.tableName,  this.columnNames.get(i), includeAudTable, Boolean.valueOf(defaultValue));
124
					this.columnAdders.add(adder);
125
				}else if (columnTypes.get(i).startsWith("string")){
126
					Integer length = Integer.valueOf(columnTypes.get(i).substring("string_".length()));
127
					ColumnAdder adder = ColumnAdder.NewStringInstance(this.getStepName(), this.tableName, this.columnNames.get(i), length, includeAudTable);
128
					this.columnAdders.add(adder);
129
				}else if (columnTypes.get(i).startsWith("clob")){
130
					ColumnAdder adder = ColumnAdder.NewClobInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable);
131
					this.columnAdders.add(adder);
132
				}else if ("tinyint".equals(columnTypes.get(i)) ){
133
					ColumnAdder adder = ColumnAdder.NewTinyIntegerInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull);
134
					this.columnAdders.add(adder);
135
				}else if ("datetime".equals(columnTypes.get(i)) ){
136
					ColumnAdder adder = ColumnAdder.NewDateTimeInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull);
137
					this.columnAdders.add(adder);
138
				}else if ("double".equals(columnTypes.get(i)) ){
139
					ColumnAdder adder = ColumnAdder.NewDoubleInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull);
140
					this.columnAdders.add(adder);
141
				}else{
142
					throw new RuntimeException("Column type " + columnTypes.get(i) + " not yet supported");
143
				}
144
			}
145
		} catch (Exception e) {
146
			throw new RuntimeException(e);
147
		}
148
	}
149

    
150
	/**
151
	 * fills the mnTablesStepList
152
	 * @param mnTablesStepList, String tableName
153
	 */
154
	public static void makeMnTables(List<ISchemaUpdaterStep> mnTablesStepList, String tableName, boolean includeAnnotatable, boolean includeIdentifiable) {
155
		TableCreator tableCreator;
156
		String stepName;
157

    
158
		if (includeAnnotatable){
159
			//annotations
160
			stepName= "Add @tableName annotations";
161
			stepName = stepName.replace("@tableName", tableName);
162
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, "Annotation", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_1_TO_M);
163
			mnTablesStepList.add(tableCreator);
164

    
165
			//marker
166
			stepName= "Add @tableName marker";
167
			stepName = stepName.replace("@tableName", tableName);
168
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, "Marker", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_1_TO_M);
169
			mnTablesStepList.add(tableCreator);
170
		}
171

    
172
		if (includeIdentifiable){
173

    
174
			//credits
175
			stepName= "Add @tableName credits";
176
			stepName = stepName.replace("@tableName", tableName);
177
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, null, "Credit", null, null, SchemaUpdaterBase.INCLUDE_AUDIT, IS_LIST, IS_1_TO_M);
178
			mnTablesStepList.add(tableCreator);
179

    
180
			//identifier
181
			stepName= "Add @tableName identifiers";
182
			stepName = stepName.replace("@tableName", tableName);
183
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, null, "Identifier", null, null, SchemaUpdaterBase.INCLUDE_AUDIT, IS_LIST, IS_1_TO_M);
184
			mnTablesStepList.add(tableCreator);
185

    
186
			//extensions
187
			stepName= "Add @tableName extensions";
188
			stepName = stepName.replace("@tableName", tableName);
189
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, "Extension", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_1_TO_M);
190
			mnTablesStepList.add(tableCreator);
191

    
192
			//OriginalSourceBase
193
			stepName= "Add @tableName sources";
194
			stepName = stepName.replace("@tableName", tableName);
195
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, null, "OriginalSourceBase", null, "sources", SchemaUpdaterBase.INCLUDE_AUDIT, ! IS_LIST, IS_1_TO_M);
196
			mnTablesStepList.add(tableCreator);
197

    
198
			//Rights
199
			stepName= "Add @tableName rights";
200
			stepName = stepName.replace("@tableName", tableName);
201
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, null, "Rights", "RightsInfo", "rights", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_M_TO_M);
202
            mnTablesStepList.add(tableCreator);
203
		}
204
	}
205

    
206

    
207
	@Override
208
	protected void invokeOnTable(String tableName, ICdmDataSource datasource,
209
	        IProgressMonitor monitor, CaseType caseType, SchemaUpdateResult result)  {
210
		try {
211
			//CREATE
212
			String updateQuery = "CREATE TABLE @tableName (";
213
			//AUDIT
214
			if (isAuditing){
215
				updateQuery += " REV integer not null, revtype " + ColumnAdder.getDatabaseColumnType(datasource, "tinyint") + ", ";
216
			}
217
			//CdmBase
218
			if (includeCdmBaseAttributes){
219
					updateQuery += " id integer NOT NULL,"
220
						+ " created " + ColumnAdder.getDatabaseColumnType(datasource, "datetime") + ", "
221
						+ " uuid varchar(36) NOT NULL,"
222
						+ (excludeVersionableAttributes? "" : " updated " + ColumnAdder.getDatabaseColumnType(datasource, "datetime") + ", ")
223
						+ " createdby_id integer,"
224
						+ (excludeVersionableAttributes ? "" : " updatedby_id integer, ");
225
			}
226
			//EventBase
227
			if (this.includeEventBase){
228
				updateQuery += "timeperiod_start varchar(255), timeperiod_end varchar(255), timeperiod_freetext varchar(255), actor_id int, description varchar(255),";
229
			}
230
			//Identifiable
231
			if (this.includeIdentifiableEntity){
232
				updateQuery += "lsid_authority varchar(255), lsid_lsid varchar(255), lsid_namespace varchar(255), lsid_object varchar(255), lsid_revision varchar(255), protectedtitlecache bit not null, titleCache varchar(255),";
233
			}
234
			//specific columns
235
			updateQuery += 	getColumnsSql(tableName, datasource, monitor);
236

    
237
			//primary and unique keys
238
			String primaryKeySql = primaryKey(isAuditing)==null ? "" : "primary key (" + primaryKey(isAuditing) + "),";
239
			String uniqueSql = unique(isAuditing)== null ? "" : "unique(" + unique(isAuditing) + "),";
240
			updateQuery += primaryKeySql + uniqueSql;
241

    
242
			//finalize
243
			updateQuery = StringUtils.chomp(updateQuery.trim(), ",") + ")";
244

    
245
			//replace
246
			updateQuery = updateQuery.replace("@tableName", tableName);
247

    
248
			//append datasource specific string
249
			updateQuery += datasource.getDatabaseType().getHibernateDialect().getTableTypeString();
250
			logger.debug("UPDATE Query: " + updateQuery);
251

    
252
			//execute
253
			datasource.executeUpdate(updateQuery);
254

    
255
			//Foreign Keys
256
			createForeignKeys(tableName, isAuditing, datasource, monitor, caseType, result);
257

    
258
			return;
259
		} catch (Exception e) {
260
			monitor.warning(e.getMessage(), e);
261
			logger.error(e);
262
			result.addException(e, e.getMessage(), "TableCreator.invokeOnTable");
263
			return;
264
		}
265
	}
266

    
267

    
268
	/**
269
	 * Returns the sql part for the {@link #columnAdders} columns.
270
	 * This is done by reusing the same method in the ColumnAdder class and removing all the prefixes like 'ADD COLUMN'
271
	 */
272
	private String getColumnsSql(String tableName, ICdmDataSource datasource, IProgressMonitor monitor) throws DatabaseTypeNotSupportedException {
273
		String result = "";
274
		for (ColumnAdder adder : this.columnAdders){
275
			String singleAdderSQL = adder.getUpdateQueryString(tableName, datasource, monitor) + ", ";
276

    
277
			String[] split = singleAdderSQL.split(ColumnAdder.getAddColumnSeperator(datasource));
278
			result += split[1];
279
		}
280
		return result;
281
	}
282

    
283

    
284
	private void createForeignKeys(String tableName, boolean isAudit, ICdmDataSource datasource,
285
	        IProgressMonitor monitor, CaseType caseType, SchemaUpdateResult result) {
286
		if (includeCdmBaseAttributes){
287
			//updatedBy
288
		    if (! this.excludeVersionableAttributes){
289
				String attribute = "updatedby";
290
				String referencedTable = "UserAccount";
291
				makeForeignKey(tableName, datasource, monitor, attribute,
292
				        referencedTable, caseType, result);
293
			}
294

    
295
		    //createdBy
296
			String attribute = "createdby";
297
			String referencedTable = "UserAccount";
298
			makeForeignKey(tableName, datasource, monitor, attribute,
299
			        referencedTable, caseType, result);
300

    
301
		}
302
		if (isAudit){
303
		    //REV
304
			String attribute = "REV";
305
			String referencedTable = "AuditEvent";
306
			makeForeignKey(tableName, datasource, monitor, attribute,
307
			        referencedTable, caseType, result);
308
		}
309
		if (this.includeEventBase){
310
			//actor
311
		    String attribute = "actor_id";
312
			String referencedTable = "AgentBase";
313
			makeForeignKey(tableName, datasource, monitor, attribute,
314
			        referencedTable, caseType, result);
315
		}
316
		for (ColumnAdder adder : this.columnAdders){
317
			if (adder.getReferencedTable() != null){
318
				makeForeignKey(tableName, datasource, monitor, adder.getNewColumnName(),
319
				        adder.getReferencedTable(), caseType, result);
320
			}
321
		}
322
		return;
323
	}
324

    
325

    
326
    public static void makeForeignKey(String tableName, ICdmDataSource datasource,
327
            IProgressMonitor monitor, String attribute, String referencedTable, CaseType caseType,
328
            SchemaUpdateResult result) {
329

    
330
		referencedTable = caseType.transformTo(referencedTable);
331

    
332
        String idSuffix = "_id";
333
        if (isRevAttribute(attribute) || attribute.endsWith(idSuffix)){
334
            idSuffix = "";
335
        }
336
        String columnName =  attribute + idSuffix;
337

    
338
		if (supportsForeignKeys(datasource, monitor, tableName, referencedTable)){
339
			String index = "FK@tableName_@attribute";
340
			index = index.replace("@tableName", tableName);
341
			index = index.replace("@attribute", attribute);
342

    
343

    
344
			//OLD - don't remember why we used ADD INDEX here
345
//			String updateQuery = "ALTER TABLE @tableName ADD INDEX @index (@attribute), ADD FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
346
			String updateQuery = "ALTER TABLE @tableName ADD @constraintName FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
347
			updateQuery = updateQuery.replace("@tableName", tableName);
348
//			updateQuery = updateQuery.replace("@index", index);
349
			updateQuery = updateQuery.replace("@attribute", columnName);
350
			updateQuery = updateQuery.replace("@referencedTable", referencedTable);
351
			if (datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
352
				updateQuery = updateQuery.replace("@constraintName", "CONSTRAINT " + index);
353
			}else{
354
				updateQuery = updateQuery.replace("@constraintName", "");  //H2 does not support "CONSTRAINT", didn't check for others
355
			}
356

    
357
			if (isRevAttribute(attribute)){
358
				updateQuery = updateQuery.replace("@id", "revisionnumber");
359
			}else{
360
				updateQuery = updateQuery.replace("@id", "id");
361
			}
362
			logger.debug(updateQuery);
363
			try {
364
				datasource.executeUpdate(updateQuery);
365
			} catch (Exception e) {
366
				String message = "Problem when creating Foreign Key for " + tableName +"." + attribute +": " + e.getMessage();
367
				monitor.warning(message);
368
				logger.warn(message, e);
369
				result.addWarning(message);
370
				return;   //we do not interrupt update if only foreign key generation did not work
371
			}
372
			return;
373
		}else{
374
		    //create only index
375
			IndexAdder indexAdder = IndexAdder.NewIntegerInstance("Add index instead of Foreign Key", tableName, columnName);
376
			try {
377
                indexAdder.invoke(datasource, monitor, caseType, result);
378
            } catch (Exception e) {
379
                String message = "Problem when creating index instead of Foreign Key for " + tableName +"." + columnName +": " + e.getMessage();
380
                monitor.warning(message);
381
                logger.warn(message, e);
382
                result.addWarning(message);
383
                return;   //we do not interrupt update if only index generation did not work
384
            }
385
		    return;
386
		}
387
	}
388

    
389
	/**
390
	 * Determines if the tables and the database support foreign keys. If determination is not possible true is returned as default.
391
	 * @param datasource
392
	 * @param monitor
393
	 * @param tableName
394
	 * @param referencedTable
395
	 * @return
396
	 */
397
	private static boolean supportsForeignKeys(ICdmDataSource datasource, IProgressMonitor monitor, String tableName, String referencedTable) {
398
		boolean result = true;
399
		if (! datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
400
			return true;
401
		}else{
402
			try {
403
				String myIsamTables = "";
404
				String format = "SELECT ENGINE FROM information_schema.TABLES where TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'";
405
				String sql = String.format(format, datasource.getDatabase(), tableName);
406
				String engine = (String)datasource.getSingleValue(sql);
407
				if (engine.equals("MyISAM")){
408
					result = false;
409
					myIsamTables = CdmUtils.concat(",", myIsamTables, tableName);
410
				}
411
				sql = String.format(format,  datasource.getDatabase(), referencedTable);
412
				engine = (String)datasource.getSingleValue(sql);
413
				if (engine.equals("MyISAM")){
414
					result = false;
415
					myIsamTables = CdmUtils.concat(",", myIsamTables, referencedTable);
416
				}
417
				if (result == false){
418
					String message = "Tables (%s) use MyISAM engine. MyISAM does not support foreign keys.";
419
					message = String.format(message, myIsamTables);
420
					monitor.warning(message);
421
				}
422
				return result;
423
			} catch (Exception e) {
424
				String message = "Problems to determine table engine for MySQL.";
425
				monitor.warning(message);
426
				return true;  //default
427
			}
428
		}
429
	}
430

    
431
	private static boolean isRevAttribute(String attribute) {
432
		return "REV".equalsIgnoreCase(attribute);
433
	}
434

    
435

    
436
	/**
437
	 * Constructs the primary key creation string
438
	 * @param isAudit
439
	 * @return
440
	 */
441
	protected String primaryKey(boolean isAudit){
442
		String result = null;
443
		if (! isAudit && this.primaryKeyParams != null){
444
			return this.primaryKeyParams;
445
		}else if (isAudit && this.primaryKeyParams_AUD != null){
446
			return this.primaryKeyParams_AUD;
447
		}
448

    
449
		if (includeCdmBaseAttributes || ! includeCdmBaseAttributes){ //TODO how to handle not CDMBase includes
450
			if (! isAudit){
451
				result = "id";
452
			}else{
453
				result = "id, REV";
454
			}
455
		}
456
		return result;
457
	}
458

    
459
	/**
460
	 * Constructs the unique key creation string
461
	 * @param isAudit
462
	 * @return
463
	 */
464
	protected String unique(boolean isAudit){
465
		if (! isAudit){
466
			if (this.uniqueParams != null){
467
				return this.uniqueParams;
468
			}
469
			if (includeCdmBaseAttributes){
470
				return "uuid"; //TODO how to handle not CDMBase includes
471
			}
472
			return null;
473
		}else{
474
			if (this.uniqueParams_AUD != null){
475
				return this.uniqueParams_AUD;
476
			}
477
			return null;
478
		}
479
	}
480

    
481
	public void setPrimaryKeyParams(String primaryKeyParams, String primaryKeyParams_AUD) {
482
		this.primaryKeyParams = primaryKeyParams;
483
		this.primaryKeyParams_AUD = primaryKeyParams_AUD;
484
	}
485

    
486
	public void setUniqueParams(String uniqueParams, String uniqueParams_AUD) {
487
		this.uniqueParams = uniqueParams;
488
		this.uniqueParams_AUD = uniqueParams_AUD;
489
	}
490
}
(27-27/35)