Project

General

Profile

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

    
12
import java.sql.SQLException;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.List;
16

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

    
20
import eu.etaxonomy.cdm.common.CdmUtils;
21
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
22
import eu.etaxonomy.cdm.database.DatabaseTypeEnum;
23
import eu.etaxonomy.cdm.database.ICdmDataSource;
24
import eu.etaxonomy.cdm.model.agent.AgentBase;
25

    
26
/**
27
 * @author a.mueller
28
 * @date 16.09.2010
29
 *
30
 */
31
public class TableCreator extends AuditedSchemaUpdaterStepBase<TableCreator> implements ISchemaUpdaterStep {
32
	private static final Logger logger = Logger.getLogger(TableCreator.class);
33
	
34
	private static final boolean SORT_INDEX = true;
35
	
36
	private List<String> columnNames;
37
	private List<String> columnTypes;
38
	private List<Object> defaultValues;
39
	private List<Boolean> isNotNull;
40
	private List<String> referencedTables;
41
	private boolean includeCdmBaseAttributes;
42
	private boolean includeIdentifiableEntity;
43
	private boolean includeAnnotatableEntity;
44
	private boolean includeEventBase;
45
	protected List<ColumnAdder> columnAdders = new ArrayList<ColumnAdder>();
46
	protected List<ISchemaUpdaterStep> mnTablesStepList = new ArrayList<ISchemaUpdaterStep>();
47
	private String primaryKeyParams;
48
	private String primaryKeyParams_AUD;
49
	private String uniqueParams;
50
	private String uniqueParams_AUD;
51

    
52
	
53
//	public static final TableCreator NewInstance(String stepName, String tableName, List<String> columnNames, List<String> columnTypes, List<Object> defaultValues, List<Boolean> isNull, boolean includeAudTable){
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);
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);
60
	}
61
	
62
	public static final TableCreator NewAnnotatableInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
63
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, false);
64
	}
65
	
66
	public static final TableCreator NewEventInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
67
		TableCreator result = new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, false);
68
		result.includeEventBase = true;
69
		return result;
70
	}
71
	
72
	public static final TableCreator NewIdentifiableInstance(String stepName, String tableName, String[] columnNames, String[] columnTypes, String[] referencedTables, boolean includeAudTable){
73
		return new TableCreator(stepName, tableName, Arrays.asList(columnNames), Arrays.asList(columnTypes), null, null, Arrays.asList(referencedTables), includeAudTable, true, true, true);
74
	}
75
	
76
	protected TableCreator(String stepName, String tableName, List<String> columnNames, List<String> columnTypes, List<Object> defaultValues, List<Boolean> isNotNull, List<String> referencedTables, 
77
			boolean includeAudTable, boolean includeCdmBaseAttributes, boolean includeAnnotatableEntity, boolean includeIdentifiableEntity) {
78
		super(stepName);
79
		this.tableName = tableName;
80
		this.columnNames = columnNames;
81
		this.columnTypes = columnTypes;
82
		this.defaultValues = defaultValues;
83
		this.isNotNull = isNotNull;
84
		this.referencedTables = referencedTables;
85
		this.includeAudTable = includeAudTable;
86
		this.includeCdmBaseAttributes = includeCdmBaseAttributes;
87
		this.includeAnnotatableEntity = includeAnnotatableEntity;
88
		this.includeIdentifiableEntity = includeIdentifiableEntity;
89
		makeColumnAdders();
90
		makeMnTables();
91
	}
92

    
93

    
94
	@Override
95
	public List<ISchemaUpdaterStep> getInnerSteps() {
96
		return mnTablesStepList;
97
	}
98

    
99
	/**
100
	 * Fills the {@link #columnAdders} list.
101
	 */
102
	private void makeColumnAdders() {
103
		if (columnNames.size() != columnTypes.size()){
104
			throw new RuntimeException ("ColumnNames and columnTypes must be of same size. Step: " + getStepName());
105
		}
106
			
107
		for (int i = 0; i < columnNames.size(); i++){
108
			boolean isNotNull = this.isNotNull == null ? false : this.isNotNull.get(i);
109
			if ("integer".equals(columnTypes.get(i)) || "int".equals(columnTypes.get(i))){
110
				String referencedTable = (this.referencedTables == null) ?  null : this.referencedTables.get(i);
111
				ColumnAdder adder = ColumnAdder.NewIntegerInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull, referencedTable);
112
				this.columnAdders.add(adder);
113
			}else if ("boolean".equals(columnTypes.get(i)) || "bit".equals(columnTypes.get(i))){
114
				String defaultValue = this.defaultValues == null ? null : this.defaultValues.get(i).toString();
115
				ColumnAdder adder = ColumnAdder.NewBooleanInstance(getStepName(), this.tableName,  this.columnNames.get(i), includeAudTable, Boolean.valueOf(defaultValue));
116
				this.columnAdders.add(adder);
117
			}else if (columnTypes.get(i).startsWith("string")){
118
				Integer length = Integer.valueOf(columnTypes.get(i).substring("string_".length()));
119
				ColumnAdder adder = ColumnAdder.NewStringInstance(this.getStepName(), this.tableName, this.columnNames.get(i), length, includeAudTable);
120
				this.columnAdders.add(adder);
121
			}else if ("tinyint".equals(columnTypes.get(i)) ){
122
				ColumnAdder adder = ColumnAdder.NewTinyIntegerInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull);
123
				this.columnAdders.add(adder);
124
			}else if ("double".equals(columnTypes.get(i)) ){
125
				ColumnAdder adder = ColumnAdder.NewDoubleInstance(this.getStepName(), this.tableName, this.columnNames.get(i), includeAudTable, isNotNull);
126
				this.columnAdders.add(adder);
127
			}
128
		}
129
	}
130
	
131
	/**
132
	 * fills the mnTablesStepList
133
	 */
134
	private void makeMnTables() {
135
		TableCreator tableCreator;
136

    
137
		if (this.includeAnnotatableEntity){
138
			//annotations
139
			stepName= "Add @tableName annotations";
140
			stepName = stepName.replace("@tableName", this.tableName);
141
			tableCreator = MnTableCreator.NewMnInstance(stepName, this.tableName, "Annotation", SchemaUpdaterBase.INCLUDE_AUDIT);
142
			mnTablesStepList.add(tableCreator);
143

    
144
			//marker
145
			stepName= "Add @tableName marker";
146
			stepName = stepName.replace("@tableName", this.tableName);
147
			tableCreator = MnTableCreator.NewMnInstance(stepName, this.tableName, "Marker", SchemaUpdaterBase.INCLUDE_AUDIT);
148
			mnTablesStepList.add(tableCreator);
149
			
150
		}
151
		
152
		if (this.includeIdentifiableEntity){
153

    
154
			//credits
155
			stepName= "Add @tableName credits";
156
			stepName = stepName.replace("@tableName", this.tableName);
157
			tableCreator = MnTableCreator.NewMnInstance(stepName, this.tableName, null, "Credit", null, SchemaUpdaterBase.INCLUDE_AUDIT, SORT_INDEX, false);
158
			mnTablesStepList.add(tableCreator);
159
			
160
			//extensions
161
			stepName= "Add @tableName extensions";
162
			stepName = stepName.replace("@tableName", this.tableName);
163
			tableCreator = MnTableCreator.NewMnInstance(stepName, this.tableName, "Extension", SchemaUpdaterBase.INCLUDE_AUDIT);
164
			mnTablesStepList.add(tableCreator);
165
			
166
			//OriginalSourceBase
167
			stepName= "Add @tableName sources";
168
			stepName = stepName.replace("@tableName", this.tableName);
169
			tableCreator = MnTableCreator.NewMnInstance(stepName, this.tableName, null, "OriginalSourceBase", "sources", SchemaUpdaterBase.INCLUDE_AUDIT, false, true);
170
			mnTablesStepList.add(tableCreator);
171

    
172
			//Rights
173
			stepName= "Add @tableName rights";
174
			stepName = stepName.replace("@tableName", this.tableName);
175
			tableCreator = MnTableCreator.NewMnInstance(stepName, this.tableName, "Rights", SchemaUpdaterBase.INCLUDE_AUDIT);
176
			mnTablesStepList.add(tableCreator);
177
		}
178
	}
179

    
180

    
181
	@Override
182
	protected boolean invokeOnTable(String tableName, ICdmDataSource datasource, IProgressMonitor monitor, CaseType caseType)  {
183
		try {
184
			boolean result = true;
185
			//CREATE
186
			String updateQuery = "CREATE TABLE @tableName (";
187
			//AUDIT
188
			if (isAuditing){
189
				updateQuery += " REV integer not null, revtype tinyint, ";
190
			}
191
			//CdmBase
192
			if (includeCdmBaseAttributes){
193
					updateQuery += " id integer not null,"
194
						+ " created datetime, "
195
						+ " uuid varchar(36),"
196
						+ " updated datetime, "
197
						+ " createdby_id integer,"
198
						+ " updatedby_id integer, ";
199
			}
200
			//EventBase
201
			if (this.includeEventBase){
202
				updateQuery += "timeperiod_start varchar(255), timeperiod_end varchar(255), timeperiod_freetext varchar(255), actor_id int, description varchar(255),";
203
			}
204
			//Identifiable
205
			if (this.includeIdentifiableEntity){
206
				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),";
207
			}
208
			//specific columns
209
			updateQuery += 	getColumnsSql(tableName, datasource, monitor);
210
			
211
			//primary and unique keys
212
			String primaryKeySql = primaryKey(isAuditing)==null ? "" : "primary key (" + primaryKey(isAuditing) + "),";
213
			String uniqueSql = unique(isAuditing)== null ? "" : "unique(" + unique(isAuditing) + "),";
214
			updateQuery += primaryKeySql + uniqueSql;
215
			
216
			//finalize
217
			updateQuery = StringUtils.chomp(updateQuery.trim(), ",") + ")";
218
			
219
			//replace
220
			updateQuery = updateQuery.replace("@tableName", tableName);
221
			
222
			//append datasource specific string
223
			updateQuery += datasource.getDatabaseType().getHibernateDialect().getTableTypeString();
224
			logger.debug("UPDATE Query: " + updateQuery);
225
			
226
			//execute
227
			datasource.executeUpdate(updateQuery);
228
			
229
			//Foreign Keys
230
			result &= createForeignKeys(tableName, isAuditing, datasource, monitor, caseType);
231
			
232
			return result;
233
		} catch (Exception e) {
234
			monitor.warning(e.getMessage(), e);
235
			logger.error(e);
236
			return false;
237
		}
238
	}
239

    
240

    
241
	/**
242
	 * Returns the sql part for the {@link #columnAdders} columns.
243
	 * This is done by reusing the same method in the ColumnAdder class and removing all the prefixes like 'ADD COLUMN'
244
	 */
245
	private String getColumnsSql(String tableName, ICdmDataSource datasource, IProgressMonitor monitor) throws DatabaseTypeNotSupportedException {
246
		String result = "";
247
		for (ColumnAdder adder : this.columnAdders){
248
			String singleAdderSQL = adder.getUpdateQueryString(tableName, datasource, monitor) + ", ";
249
			
250
			String[] split = singleAdderSQL.split(ColumnAdder.getAddColumnSeperator(datasource));
251
			result += split[1];
252
		}
253
		return result;
254
	}
255

    
256
	
257
	private boolean createForeignKeys(String tableName, boolean isAudit, ICdmDataSource datasource, IProgressMonitor monitor, CaseType caseType) throws SQLException {
258
		boolean result = true;
259
		if (includeCdmBaseAttributes){
260
			String attribute = "updatedby";
261
			String referencedTable = "UserAccount";
262
			result &= makeForeignKey(tableName, datasource, monitor, attribute, referencedTable, caseType);
263
			
264
			attribute = "createdby";
265
			referencedTable = "UserAccount";
266
			result &= makeForeignKey(tableName, datasource, monitor, attribute, referencedTable, caseType);			
267
		
268
		}
269
		if (isAudit){
270
			String attribute = "REV";
271
			String referencedTable = "AuditEvent";
272
			result &= makeForeignKey(tableName, datasource, monitor, attribute, referencedTable, caseType);
273
		}
274
		if (this.includeEventBase){
275
			String attribute = "actor_id";
276
			String referencedTable = "AgentBase";
277
			result &= makeForeignKey(tableName, datasource, monitor, attribute, referencedTable, caseType);
278
		}
279
		for (ColumnAdder adder : this.columnAdders){
280
			if (adder.getReferencedTable() != null){
281
				result &= makeForeignKey(tableName, datasource, monitor, adder.getNewColumnName(), adder.getReferencedTable(), caseType); 
282
			}
283
		}
284
		return result;
285
	}
286

    
287
	public static boolean makeForeignKey(String tableName, ICdmDataSource datasource, IProgressMonitor monitor, String attribute, String referencedTable, CaseType caseType) throws SQLException {
288
		boolean result = true;
289
		
290
		referencedTable = caseType.transformTo(referencedTable);
291
		
292
		if (supportsForeignKeys(datasource, monitor, tableName, referencedTable)){
293
			String index = "FK@tableName_@attribute";
294
			index = index.replace("@tableName", tableName);
295
			index = index.replace("@attribute", attribute);
296
			
297
			String idSuffix = "_id";
298
			if (isRevAttribute(attribute) || attribute.endsWith(idSuffix)){
299
				idSuffix = "";
300
			}
301
			//OLD - don't remember why we used ADD INDEX here
302
//			String updateQuery = "ALTER TABLE @tableName ADD INDEX @index (@attribute), ADD FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
303
			String updateQuery = "ALTER TABLE @tableName ADD @constraintName FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
304
			updateQuery = updateQuery.replace("@tableName", tableName);
305
//			updateQuery = updateQuery.replace("@index", index);
306
			updateQuery = updateQuery.replace("@attribute", attribute + idSuffix);
307
			updateQuery = updateQuery.replace("@referencedTable", referencedTable);
308
			if (datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
309
				updateQuery = updateQuery.replace("@constraintName", "CONSTRAINT " + index);
310
			}else{
311
				updateQuery = updateQuery.replace("@constraintName", "");  //H2 does not support "CONSTRAINT", didn't check for others
312
			}
313
			
314
			if (isRevAttribute(attribute)){
315
				updateQuery = updateQuery.replace("@id", "revisionnumber");
316
			}else{
317
				updateQuery = updateQuery.replace("@id", "id");
318
			}
319
			logger.debug(updateQuery);
320
			try {
321
				datasource.executeUpdate(updateQuery);
322
			} catch (Exception e) {
323
				String message = "Problem when creating Foreign Key for " + tableName +"." + attribute +": " + e.getMessage();
324
				monitor.warning(message);
325
				logger.warn(message, e);
326
				return true;   //we do not interrupt update if only foreign key generation did not work
327
			}
328
			return result;			
329
		}else{
330
			return true;
331
		}
332

    
333
	}
334

    
335
	/**
336
	 * Determines if the tables and the database support foreign keys. If determination is not possible true is returned as default.
337
	 * @param datasource
338
	 * @param monitor
339
	 * @param tableName
340
	 * @param referencedTable
341
	 * @return
342
	 */
343
	private static boolean supportsForeignKeys(ICdmDataSource datasource, IProgressMonitor monitor, String tableName, String referencedTable) {
344
		boolean result = true;
345
		if (! datasource.getDatabaseType().equals(DatabaseTypeEnum.MySQL)){
346
			return true;
347
		}else{
348
			try {
349
				String myIsamTables = "";
350
				String format = "SELECT ENGINE FROM information_schema.TABLES where TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'";
351
				String sql = String.format(format, datasource.getDatabase(), tableName);
352
				String engine = (String)datasource.getSingleValue(sql);
353
				if (engine.equals("MyISAM")){
354
					result = false;
355
					myIsamTables = CdmUtils.concat(",", myIsamTables, tableName);
356
				}
357
				sql = String.format(format,  datasource.getDatabase(), referencedTable);
358
				engine = (String)datasource.getSingleValue(sql);
359
				if (engine.equals("MyISAM")){
360
					result = false;
361
					myIsamTables = CdmUtils.concat(",", myIsamTables, referencedTable);
362
				}
363
				if (result == false){
364
					String message = "Tables (%s) use MyISAM engine. MyISAM does not support foreign keys.";
365
					message = String.format(message, myIsamTables);
366
					monitor.warning(message);
367
				}
368
				return result;
369
			} catch (Exception e) {
370
				String message = "Problems to determine table engine for MySQL.";
371
				monitor.warning(message);
372
				return true;  //default 
373
			}
374
			
375
		}
376
		
377
				
378
	}
379

    
380
	private static boolean isRevAttribute(String attribute) {
381
		return "REV".equalsIgnoreCase(attribute);
382
	}
383

    
384

    
385
	/**
386
	 * Constructs the primary key creation string
387
	 * @param isAudit
388
	 * @return
389
	 */
390
	protected String primaryKey(boolean isAudit){
391
		String result = null;
392
		if (! isAudit && this.primaryKeyParams != null){ 
393
			return this.primaryKeyParams;
394
		}else if (isAudit && this.primaryKeyParams_AUD != null){ 
395
			return this.primaryKeyParams_AUD;
396
		} 
397

    
398
		if (includeCdmBaseAttributes || ! includeCdmBaseAttributes){ //TODO how to handle not CDMBase includes
399
			if (! isAudit){
400
				result = "id";
401
			}else{
402
				result = "id, REV";
403
			}
404
		}
405
		return result;
406
	}
407
	
408
	/**
409
	 * Constructs the unique key creation string
410
	 * @param isAudit
411
	 * @return
412
	 */
413
	protected String unique(boolean isAudit){
414
		if (! isAudit){
415
			if (this.uniqueParams != null){
416
				return this.uniqueParams;
417
			}
418
			if (includeCdmBaseAttributes){
419
				return "uuid"; //TODO how to handle not CDMBase includes
420
			}
421
			return null;
422
		}else{
423
			if (this.uniqueParams_AUD != null){
424
				return this.uniqueParams_AUD;
425
			}
426
			return null;
427
		}
428
	}
429

    
430
	public void setPrimaryKeyParams(String primaryKeyParams, String primaryKeyParams_AUD) {
431
		this.primaryKeyParams = primaryKeyParams;
432
		this.primaryKeyParams_AUD = primaryKeyParams_AUD;
433
	}
434

    
435
	public void setUniqueParams(String uniqueParams, String uniqueParams_AUD) {
436
		this.uniqueParams = uniqueParams;
437
		this.uniqueParams_AUD = uniqueParams_AUD;
438
	}
439
}
(23-23/32)