Project

General

Profile

Download (21.1 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(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, List<String> columnNames, List<String> columnTypes, boolean includeAudTable, boolean includeCdmBaseAttributes){
55
		return new TableCreator(stepList, stepName, tableName, columnNames, columnTypes, null, null, null, includeAudTable, includeCdmBaseAttributes, false, false, false);
56
	}
57

    
58
	public static final TableCreator NewInstance(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable, boolean includeCdmBaseAttributes){
59
		return new TableCreator(stepList, 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 NewAuditedCdmBaseInstance(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables){
63
        return new TableCreator(stepList, stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), true, true, false, false, true);
64
    }
65

    
66
	public static final TableCreator NewNonVersionableInstance(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables){
67
		return new TableCreator(stepList, stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), false, true, false, false, true);
68
	}
69

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

    
74
	public static final TableCreator NewAnnotatableInstance(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
75
		return new TableCreator(stepList, stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, false, false);
76
	}
77

    
78
	public static final TableCreator NewEventInstance(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
79
		TableCreator result = new TableCreator(stepList, stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, false, false);
80
		result.includeEventBase = true;
81
		return result;
82
	}
83

    
84
	public static final TableCreator NewIdentifiableInstance(List<ISchemaUpdaterStep> stepList, String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
85
		return new TableCreator(stepList, stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, true, false);
86
	}
87

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

    
104

    
105
	@Override
106
	public List<ISchemaUpdaterStep> getInnerSteps() {
107
		return mnTablesStepList;
108
	}
109

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

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

    
147
	/**
148
	 * fills the mnTablesStepList
149
	 * @param mnTablesStepList, String tableName
150
	 */
151
	public static void makeMnTables(List<ISchemaUpdaterStep> mnTablesStepList, String tableName, boolean includeAnnotatable, boolean includeIdentifiable) {
152

    
153
	    String stepName;
154

    
155
		if (includeAnnotatable){
156
			//annotations
157
			stepName= "Add @tableName annotations";
158
			stepName = stepName.replace("@tableName", tableName);
159
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, "Annotation", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_1_TO_M);
160

    
161
			//marker
162
			stepName= "Add @tableName marker";
163
			stepName = stepName.replace("@tableName", tableName);
164
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, "Marker", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_1_TO_M);
165
		}
166

    
167
		if (includeIdentifiable){
168

    
169
			//credits
170
			stepName= "Add @tableName credits";
171
			stepName = stepName.replace("@tableName", tableName);
172
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, null, "Credit", null, null, SchemaUpdaterBase.INCLUDE_AUDIT, IS_LIST, IS_1_TO_M);
173

    
174
			//identifier
175
			stepName= "Add @tableName identifiers";
176
			stepName = stepName.replace("@tableName", tableName);
177
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, null, "Identifier", null, null, SchemaUpdaterBase.INCLUDE_AUDIT, IS_LIST, IS_1_TO_M);
178

    
179
			//extensions
180
			stepName= "Add @tableName extensions";
181
			stepName = stepName.replace("@tableName", tableName);
182
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, "Extension", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_1_TO_M);
183

    
184
			//OriginalSourceBase
185
			stepName= "Add @tableName sources";
186
			stepName = stepName.replace("@tableName", tableName);
187
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, null, "OriginalSourceBase", null, "sources", SchemaUpdaterBase.INCLUDE_AUDIT, ! IS_LIST, IS_1_TO_M);
188

    
189
			//Rights
190
			stepName= "Add @tableName rights";
191
			stepName = stepName.replace("@tableName", tableName);
192
			MnTableCreator.NewMnInstance(mnTablesStepList, stepName, tableName, null, "RightsInfo", null, "rights", SchemaUpdaterBase.INCLUDE_AUDIT, !IS_LIST, IS_M_TO_M);
193
		}
194
	}
195

    
196

    
197
	@Override
198
	protected void invokeOnTable(String tableName, ICdmDataSource datasource,
199
	        IProgressMonitor monitor, CaseType caseType, SchemaUpdateResult result)  {
200
		try {
201
			//CREATE
202
			String updateQuery = "CREATE TABLE @tableName (";
203
			//AUDIT
204
			if (isAuditing){
205
				updateQuery += " REV integer not null, revtype " + Datatype.TINYINTEGER.format(datasource, null) + ", ";
206
			}
207
			//CdmBase
208
			if (includeCdmBaseAttributes){
209
					String uuidNull = isAuditing? "": "NOT NULL";
210
			        updateQuery += " id integer NOT NULL,"
211
						+ " created " + Datatype.DATETIME.format(datasource, null) + ", "
212
						+ " uuid varchar(36) "+uuidNull+","
213
						+ (excludeVersionableAttributes? "" : " updated " + Datatype.DATETIME.format(datasource, null) + ", ")
214
						+ " createdby_id integer,"
215
						+ (excludeVersionableAttributes ? "" : " updatedby_id integer, ");
216
			}
217
			//EventBase
218
			if (this.includeEventBase){
219
				updateQuery += "timeperiod_start varchar(255), timeperiod_end varchar(255), timeperiod_freetext varchar(255), actor_id int, description varchar(255),";
220
			}
221
			//Identifiable
222
			if (this.includeIdentifiableEntity){
223
				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),";
224
			}
225
			//specific columns
226
			updateQuery += 	getColumnsSql(tableName, datasource, monitor);
227

    
228
			//primary and unique keys
229
			String primaryKeySql = primaryKey(isAuditing)==null ? "" : "primary key (" + primaryKey(isAuditing) + "),";
230
			String uniqueSql = unique(isAuditing)== null ? "" : "unique(" + unique(isAuditing) + "),";
231
			updateQuery += primaryKeySql + uniqueSql;
232

    
233
			//finalize
234
			updateQuery = StringUtils.chomp(updateQuery.trim(), ",") + ")";
235

    
236
			//replace
237
			updateQuery = updateQuery.replace("@tableName", tableName);
238

    
239
			//append datasource specific string
240
			updateQuery += datasource.getDatabaseType().getHibernateDialect().getTableTypeString();
241
			logger.debug("UPDATE Query: " + updateQuery);
242

    
243
			//execute
244
			datasource.executeUpdate(updateQuery);
245

    
246
			//Foreign Keys
247
			createForeignKeys(tableName, isAuditing, datasource, monitor, caseType, result);
248

    
249
			return;
250
		} catch (Exception e) {
251
			monitor.warning(e.getMessage(), e);
252
			logger.error(e);
253
			result.addException(e, e.getMessage(), "TableCreator.invokeOnTable");
254
			return;
255
		}
256
	}
257

    
258

    
259
	/**
260
	 * Returns the sql part for the {@link #columnAdders} columns.
261
	 * This is done by reusing the same method in the ColumnAdder class and removing all the prefixes like 'ADD COLUMN'
262
	 */
263
	private String getColumnsSql(String tableName, ICdmDataSource datasource, IProgressMonitor monitor) throws DatabaseTypeNotSupportedException {
264
		String result = "";
265
		for (ColumnAdder adder : this.columnAdders){
266
			String singleAdderSQL = adder.getUpdateQueryString(tableName, datasource, monitor) + ", ";
267

    
268
			String[] split = singleAdderSQL.split(ColumnAdder.getAddColumnSeperator(datasource));
269
			result += split[1];
270
		}
271
		return result;
272
	}
273

    
274

    
275
	private void createForeignKeys(String tableName, boolean isAudit, ICdmDataSource datasource,
276
	        IProgressMonitor monitor, CaseType caseType, SchemaUpdateResult result) {
277
		if (includeCdmBaseAttributes){
278
			//updatedBy
279
		    if (! this.excludeVersionableAttributes){
280
				String attribute = "updatedby";
281
				String referencedTable = "UserAccount";
282
				makeForeignKey(tableName, datasource, monitor, attribute,
283
				        referencedTable, caseType, result);
284
			}
285

    
286
		    //createdBy
287
			String attribute = "createdby";
288
			String referencedTable = "UserAccount";
289
			makeForeignKey(tableName, datasource, monitor, attribute,
290
			        referencedTable, caseType, result);
291

    
292
		}
293
		if (isAudit){
294
		    //REV
295
			String attribute = "REV";
296
			String referencedTable = "AuditEvent";
297
			makeForeignKey(tableName, datasource, monitor, attribute,
298
			        referencedTable, caseType, result);
299
		}
300
		if (this.includeEventBase){
301
			//actor
302
		    String attribute = "actor_id";
303
			String referencedTable = "AgentBase";
304
			makeForeignKey(tableName, datasource, monitor, attribute,
305
			        referencedTable, caseType, result);
306
		}
307
		for (ColumnAdder adder : this.columnAdders){
308
			if (adder.getReferencedTable() != null){
309
				makeForeignKey(tableName, datasource, monitor, adder.getNewColumnName(),
310
				        adder.getReferencedTable(), caseType, result);
311
			}
312
		}
313
		return;
314
	}
315

    
316

    
317
    public static void makeForeignKey(String tableName, ICdmDataSource datasource,
318
            IProgressMonitor monitor, String attribute, String referencedTable, CaseType caseType,
319
            SchemaUpdateResult result) {
320

    
321
		referencedTable = caseType.transformTo(referencedTable);
322

    
323
        String idSuffix = "_id";
324
        if (isRevAttribute(attribute) || attribute.endsWith(idSuffix)){
325
            idSuffix = "";
326
        }
327
        String columnName =  attribute + idSuffix;
328

    
329
		if (supportsForeignKeys(datasource, monitor, tableName, referencedTable)){
330
			String index = "FK@tableName_@attribute";
331
			index = index.replace("@tableName", tableName);
332
			index = index.replace("@attribute", attribute);
333

    
334

    
335
			//OLD - don't remember why we used ADD INDEX here
336
//			String updateQuery = "ALTER TABLE @tableName ADD INDEX @index (@attribute), ADD FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
337
			String updateQuery = "ALTER TABLE @tableName ADD @constraintName FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
338
			updateQuery = updateQuery.replace("@tableName", tableName);
339
//			updateQuery = updateQuery.replace("@index", index);
340
			updateQuery = updateQuery.replace("@attribute", columnName);
341
			updateQuery = updateQuery.replace("@referencedTable", referencedTable);
342
			if (datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
343
				updateQuery = updateQuery.replace("@constraintName", "CONSTRAINT " + index);
344
			}else{
345
				updateQuery = updateQuery.replace("@constraintName", "");  //H2 does not support "CONSTRAINT", didn't check for others
346
			}
347

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

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

    
422
	private static boolean isRevAttribute(String attribute) {
423
		return "REV".equalsIgnoreCase(attribute);
424
	}
425

    
426

    
427
	/**
428
	 * Constructs the primary key creation string
429
	 * @param isAudit
430
	 * @return
431
	 */
432
	protected String primaryKey(boolean isAudit){
433
		String result = null;
434
		if (! isAudit && this.primaryKeyParams != null){
435
			return this.primaryKeyParams;
436
		}else if (isAudit && this.primaryKeyParams_AUD != null){
437
			return this.primaryKeyParams_AUD;
438
		}
439

    
440
		if (includeCdmBaseAttributes || ! includeCdmBaseAttributes){ //TODO how to handle not CDMBase includes => via this.primaryKeyParams
441
			if (! isAudit){
442
				result = "id";
443
			}else{
444
				result = "id, REV";
445
			}
446
		}
447
		return result;
448
	}
449

    
450
	/**
451
	 * Constructs the unique key creation string
452
	 * @param isAudit
453
	 * @return
454
	 */
455
	protected String unique(boolean isAudit){
456
		if (! isAudit){
457
			if (this.uniqueParams != null){
458
				return this.uniqueParams;
459
			}
460
			if (includeCdmBaseAttributes){
461
				return "uuid"; //TODO how to handle not CDMBase includes
462
			}
463
			return null;
464
		}else{
465
			if (this.uniqueParams_AUD != null){
466
				return this.uniqueParams_AUD;
467
			}
468
			return null;
469
		}
470
	}
471

    
472
	public TableCreator setPrimaryKeyParams(String primaryKeyParams, String primaryKeyParams_AUD) {
473
		this.primaryKeyParams = primaryKeyParams;
474
		this.primaryKeyParams_AUD = primaryKeyParams_AUD;
475
		return this;
476
	}
477

    
478
	public TableCreator setUniqueParams(String uniqueParams, String uniqueParams_AUD) {
479
		this.uniqueParams = uniqueParams;
480
		this.uniqueParams_AUD = uniqueParams_AUD;
481
		return this;
482
	}
483
}
(32-32/41)