Project

General

Profile

Download (20 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.sql.SQLException;
12
import java.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.List;
15

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

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

    
24
/**
25
 * @author a.mueller
26
 * @date 16.09.2010
27
 *
28
 */
29
public class TableCreator extends AuditedSchemaUpdaterStepBase<TableCreator> implements ISchemaUpdaterStep {
30
	private static final Logger logger = Logger.getLogger(TableCreator.class);
31

    
32
	private static final boolean SORT_INDEX = true;
33

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

    
51

    
52
//	public static final TableCreator NewInstance(String stepName, String tableName, List<String> columnNames, List<String> columnTypes, List<Object> defaultValues, List<Boolean> isNull, boolean includeAudTable){
53
	public static final TableCreator NewInstance(String stepName, String tableName, List<String> columnNames, List<String> columnTypes, boolean includeAudTable, boolean includeCdmBaseAttributes){
54
		return new TableCreator(stepName, tableName, columnNames, columnTypes, null, null, null, includeAudTable, includeCdmBaseAttributes, false, false, false);
55
	}
56

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

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

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

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

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

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

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

    
99

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

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

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

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

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

    
164
			//marker
165
			stepName= "Add @tableName marker";
166
			stepName = stepName.replace("@tableName", tableName);
167
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, "Marker", SchemaUpdaterBase.INCLUDE_AUDIT);
168
			mnTablesStepList.add(tableCreator);
169

    
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, SchemaUpdaterBase.INCLUDE_AUDIT, SORT_INDEX, false);
178
			mnTablesStepList.add(tableCreator);
179

    
180

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

    
187

    
188
			//extensions
189
			stepName= "Add @tableName extensions";
190
			stepName = stepName.replace("@tableName", tableName);
191
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, "Extension", SchemaUpdaterBase.INCLUDE_AUDIT);
192
			mnTablesStepList.add(tableCreator);
193

    
194
			//OriginalSourceBase
195
			stepName= "Add @tableName sources";
196
			stepName = stepName.replace("@tableName", tableName);
197
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, null, "OriginalSourceBase", "sources", SchemaUpdaterBase.INCLUDE_AUDIT, false, true);
198
			mnTablesStepList.add(tableCreator);
199

    
200
			//Rights
201
			stepName= "Add @tableName rights";
202
			stepName = stepName.replace("@tableName", tableName);
203
			tableCreator = MnTableCreator.NewMnInstance(stepName, tableName, "Rights", SchemaUpdaterBase.INCLUDE_AUDIT);
204
			mnTablesStepList.add(tableCreator);
205
		}
206
	}
207

    
208

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

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

    
244
			//finalize
245
			updateQuery = StringUtils.chomp(updateQuery.trim(), ",") + ")";
246

    
247
			//replace
248
			updateQuery = updateQuery.replace("@tableName", tableName);
249

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

    
254
			//execute
255
			datasource.executeUpdate(updateQuery);
256

    
257
			//Foreign Keys
258
			result &= createForeignKeys(tableName, isAuditing, datasource, monitor, caseType);
259

    
260
			return result;
261
		} catch (Exception e) {
262
			monitor.warning(e.getMessage(), e);
263
			logger.error(e);
264
			return false;
265
		}
266
	}
267

    
268

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

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

    
284

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

    
294
			String attribute = "createdby";
295
			String referencedTable = "UserAccount";
296
			result &= makeForeignKey(tableName, datasource, monitor, attribute, referencedTable, caseType);
297

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

    
317
	public static boolean makeForeignKey(String tableName, ICdmDataSource datasource, IProgressMonitor monitor, String attribute, String referencedTable, CaseType caseType) throws SQLException {
318
		boolean result = true;
319

    
320
		referencedTable = caseType.transformTo(referencedTable);
321

    
322
		if (supportsForeignKeys(datasource, monitor, tableName, referencedTable)){
323
			String index = "FK@tableName_@attribute";
324
			index = index.replace("@tableName", tableName);
325
			index = index.replace("@attribute", attribute);
326

    
327
			String idSuffix = "_id";
328
			if (isRevAttribute(attribute) || attribute.endsWith(idSuffix)){
329
				idSuffix = "";
330
			}
331
			//OLD - don't remember why we used ADD INDEX here
332
//			String updateQuery = "ALTER TABLE @tableName ADD INDEX @index (@attribute), ADD FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
333
			String updateQuery = "ALTER TABLE @tableName ADD @constraintName FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
334
			updateQuery = updateQuery.replace("@tableName", tableName);
335
//			updateQuery = updateQuery.replace("@index", index);
336
			updateQuery = updateQuery.replace("@attribute", attribute + idSuffix);
337
			updateQuery = updateQuery.replace("@referencedTable", referencedTable);
338
			if (datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
339
				updateQuery = updateQuery.replace("@constraintName", "CONSTRAINT " + index);
340
			}else{
341
				updateQuery = updateQuery.replace("@constraintName", "");  //H2 does not support "CONSTRAINT", didn't check for others
342
			}
343

    
344
			if (isRevAttribute(attribute)){
345
				updateQuery = updateQuery.replace("@id", "revisionnumber");
346
			}else{
347
				updateQuery = updateQuery.replace("@id", "id");
348
			}
349
			logger.debug(updateQuery);
350
			try {
351
				datasource.executeUpdate(updateQuery);
352
			} catch (Exception e) {
353
				String message = "Problem when creating Foreign Key for " + tableName +"." + attribute +": " + e.getMessage();
354
				monitor.warning(message);
355
				logger.warn(message, e);
356
				return true;   //we do not interrupt update if only foreign key generation did not work
357
			}
358
			return result;
359
		}else{
360
			return true;
361
		}
362

    
363
	}
364

    
365
	/**
366
	 * Determines if the tables and the database support foreign keys. If determination is not possible true is returned as default.
367
	 * @param datasource
368
	 * @param monitor
369
	 * @param tableName
370
	 * @param referencedTable
371
	 * @return
372
	 */
373
	private static boolean supportsForeignKeys(ICdmDataSource datasource, IProgressMonitor monitor, String tableName, String referencedTable) {
374
		boolean result = true;
375
		if (! datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
376
			return true;
377
		}else{
378
			try {
379
				String myIsamTables = "";
380
				String format = "SELECT ENGINE FROM information_schema.TABLES where TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'";
381
				String sql = String.format(format, datasource.getDatabase(), tableName);
382
				String engine = (String)datasource.getSingleValue(sql);
383
				if (engine.equals("MyISAM")){
384
					result = false;
385
					myIsamTables = CdmUtils.concat(",", myIsamTables, tableName);
386
				}
387
				sql = String.format(format,  datasource.getDatabase(), referencedTable);
388
				engine = (String)datasource.getSingleValue(sql);
389
				if (engine.equals("MyISAM")){
390
					result = false;
391
					myIsamTables = CdmUtils.concat(",", myIsamTables, referencedTable);
392
				}
393
				if (result == false){
394
					String message = "Tables (%s) use MyISAM engine. MyISAM does not support foreign keys.";
395
					message = String.format(message, myIsamTables);
396
					monitor.warning(message);
397
				}
398
				return result;
399
			} catch (Exception e) {
400
				String message = "Problems to determine table engine for MySQL.";
401
				monitor.warning(message);
402
				return true;  //default
403
			}
404

    
405
		}
406

    
407

    
408
	}
409

    
410
	private static boolean isRevAttribute(String attribute) {
411
		return "REV".equalsIgnoreCase(attribute);
412
	}
413

    
414

    
415
	/**
416
	 * Constructs the primary key creation string
417
	 * @param isAudit
418
	 * @return
419
	 */
420
	protected String primaryKey(boolean isAudit){
421
		String result = null;
422
		if (! isAudit && this.primaryKeyParams != null){
423
			return this.primaryKeyParams;
424
		}else if (isAudit && this.primaryKeyParams_AUD != null){
425
			return this.primaryKeyParams_AUD;
426
		}
427

    
428
		if (includeCdmBaseAttributes || ! includeCdmBaseAttributes){ //TODO how to handle not CDMBase includes
429
			if (! isAudit){
430
				result = "id";
431
			}else{
432
				result = "id, REV";
433
			}
434
		}
435
		return result;
436
	}
437

    
438
	/**
439
	 * Constructs the unique key creation string
440
	 * @param isAudit
441
	 * @return
442
	 */
443
	protected String unique(boolean isAudit){
444
		if (! isAudit){
445
			if (this.uniqueParams != null){
446
				return this.uniqueParams;
447
			}
448
			if (includeCdmBaseAttributes){
449
				return "uuid"; //TODO how to handle not CDMBase includes
450
			}
451
			return null;
452
		}else{
453
			if (this.uniqueParams_AUD != null){
454
				return this.uniqueParams_AUD;
455
			}
456
			return null;
457
		}
458
	}
459

    
460
	public void setPrimaryKeyParams(String primaryKeyParams, String primaryKeyParams_AUD) {
461
		this.primaryKeyParams = primaryKeyParams;
462
		this.primaryKeyParams_AUD = primaryKeyParams_AUD;
463
	}
464

    
465
	public void setUniqueParams(String uniqueParams, String uniqueParams_AUD) {
466
		this.uniqueParams = uniqueParams;
467
		this.uniqueParams_AUD = uniqueParams_AUD;
468
	}
469
}
(27-27/36)