2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.database
.update
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Arrays
;
13 import java
.util
.List
;
15 import org
.apache
.commons
.lang
.StringUtils
;
16 import org
.apache
.logging
.log4j
.LogManager
;import org
.apache
.logging
.log4j
.Logger
;
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
;
27 public class TableCreator
extends AuditedSchemaUpdaterStepBase
{
28 private static final Logger logger
= LogManager
.getLogger(TableCreator
.class);
30 private static final boolean IS_LIST
= true;
31 private static final boolean IS_1_TO_M
= true;
32 private static final boolean IS_M_TO_M
= false;
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 final boolean includeSingleSourcedEntity
;
43 private boolean includeEventBase
;
44 private final boolean excludeVersionableAttributes
;
45 protected List
<ColumnAdder
> columnAdders
= new ArrayList
<>();
46 protected List
<ISchemaUpdaterStep
> mnTablesStepList
= new ArrayList
<>();
47 private String primaryKeyParams
;
48 private String primaryKeyParams_AUD
;
49 private String uniqueParams
;
50 private String uniqueParams_AUD
;
53 public static final TableCreator
NewInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, List
<String
> columnNames
, List
<String
> columnTypes
, boolean includeAudTable
, boolean includeCdmBaseAttributes
){
54 return new TableCreator(stepList
, stepName
, tableName
, columnNames
, columnTypes
, null, null, null, includeAudTable
, includeCdmBaseAttributes
, false, false, false, false);
57 public static final TableCreator
NewInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
, boolean includeAudTable
, boolean includeCdmBaseAttributes
){
58 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), includeAudTable
, includeCdmBaseAttributes
, false, false, false, false);
61 public static final TableCreator
NewAuditedCdmBaseInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
){
62 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), true, true, false, false, true, false);
65 public static final TableCreator
NewNonVersionableInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
){
66 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), false, true, false, false, true, false);
69 public static final TableCreator
NewVersionableInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
, boolean includeAudTable
){
70 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), includeAudTable
, true, false, false, false, false);
73 public static final TableCreator
NewAnnotatableInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
, boolean includeAudTable
){
74 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), includeAudTable
, true, true, false, false, false);
77 public static final TableCreator
NewSingleSourcedInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
, boolean includeAudTable
){
78 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), includeAudTable
, true, true, false, false, true);
81 public static final TableCreator
NewEventInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
, boolean includeAudTable
){
82 TableCreator result
= new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), includeAudTable
, true, true, false, false, false);
83 result
.includeEventBase
= true;
87 public static final TableCreator
NewIdentifiableInstance(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, String
[] columnNames
, String
[] columnTypes
, String
[] referencedTables
, boolean includeAudTable
){
88 return new TableCreator(stepList
, stepName
, tableName
, Arrays
.asList(columnNames
), Arrays
.asList(columnTypes
), null, null, Arrays
.asList(referencedTables
), includeAudTable
, true, true, true, false, false);
91 protected TableCreator(List
<ISchemaUpdaterStep
> stepList
, String stepName
, String tableName
, List
<String
> columnNames
, List
<String
> columnTypes
, List
<Object
> defaultValues
, List
<Boolean
> isNotNull
, List
<String
> referencedTables
,
92 boolean includeAudTable
, boolean includeCdmBaseAttributes
, boolean includeAnnotatableEntity
, boolean includeIdentifiableEntity
, boolean excludeVersionableAttributes
, boolean includeSingleSourcedEntity
) {
93 super(stepList
, stepName
, tableName
, includeAudTable
);
94 this.columnNames
= columnNames
;
95 this.columnTypes
= columnTypes
;
96 this.defaultValues
= defaultValues
;
97 this.isNotNull
= isNotNull
;
98 this.referencedTables
= referencedTables
;
99 this.includeCdmBaseAttributes
= includeCdmBaseAttributes
;
100 this.includeAnnotatableEntity
= includeAnnotatableEntity
;
101 this.includeIdentifiableEntity
= includeIdentifiableEntity
;
102 this.excludeVersionableAttributes
= excludeVersionableAttributes
;
103 this.includeSingleSourcedEntity
= includeSingleSourcedEntity
;
105 makeMnTables(mnTablesStepList
, this.tableName
, this.includeAnnotatableEntity
, this.includeIdentifiableEntity
);
110 public List
<ISchemaUpdaterStep
> getInnerSteps() {
111 return mnTablesStepList
;
115 * Fills the {@link #columnAdders} list.
117 private void makeColumnAdders() {
118 if (columnNames
.size() != columnTypes
.size()){
119 throw new RuntimeException ("ColumnNames and columnTypes must be of same size. Step: " + getStepName());
123 for (int i
= 0; i
< columnNames
.size(); i
++){
124 boolean isNotNull
= this.isNotNull
== null ?
false : this.isNotNull
.get(i
);
125 if ("integer".equals(columnTypes
.get(i
)) || "int".equals(columnTypes
.get(i
))){
126 String referencedTable
= (this.referencedTables
== null) ?
null : this.referencedTables
.get(i
);
127 ColumnAdder
.NewIntegerInstance(columnAdders
, this.getStepName(), this.tableName
, this.columnNames
.get(i
), includeAudTable
, isNotNull
, referencedTable
);
128 }else if ("boolean".equals(columnTypes
.get(i
)) || "bit".equals(columnTypes
.get(i
))){
129 String defaultValue
= this.defaultValues
== null ?
null : this.defaultValues
.get(i
).toString();
130 ColumnAdder
.NewBooleanInstance(columnAdders
, getStepName(), this.tableName
, this.columnNames
.get(i
), includeAudTable
, Boolean
.valueOf(defaultValue
));
131 }else if (columnTypes
.get(i
).startsWith("string")){
132 Integer length
= Integer
.valueOf(columnTypes
.get(i
).substring("string_".length()));
133 ColumnAdder
.NewStringInstance(columnAdders
, getStepName(), this.tableName
, this.columnNames
.get(i
), length
, includeAudTable
);
134 }else if (columnTypes
.get(i
).startsWith("clob")){
135 ColumnAdder
.NewClobInstance(columnAdders
, this.getStepName(), this.tableName
, this.columnNames
.get(i
), includeAudTable
);
136 }else if ("tinyint".equals(columnTypes
.get(i
)) ){
137 ColumnAdder
.NewTinyIntegerInstance(columnAdders
, this.getStepName(), this.tableName
, this.columnNames
.get(i
), includeAudTable
, isNotNull
);
138 }else if ("datetime".equals(columnTypes
.get(i
)) ){
139 ColumnAdder
.NewDateTimeInstance(columnAdders
, this.getStepName(), this.tableName
, this.columnNames
.get(i
), includeAudTable
, isNotNull
);
140 }else if ("double".equals(columnTypes
.get(i
)) ){
141 ColumnAdder
.NewDoubleInstance(columnAdders
, this.getStepName(), this.tableName
, this.columnNames
.get(i
), includeAudTable
, isNotNull
);
143 throw new RuntimeException("Column type " + columnTypes
.get(i
) + " not yet supported");
146 } catch (Exception e
) {
147 throw new RuntimeException(e
);
152 * fills the mnTablesStepList
154 public static void makeMnTables(List
<ISchemaUpdaterStep
> mnTablesStepList
, String tableName
, boolean includeAnnotatable
, boolean includeIdentifiable
) {
158 if (includeAnnotatable
){
160 stepName
= "Add @tableName annotations";
161 stepName
= stepName
.replace("@tableName", tableName
);
162 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, "Annotation", SchemaUpdaterBase
.INCLUDE_AUDIT
, !IS_LIST
, IS_1_TO_M
);
165 stepName
= "Add @tableName marker";
166 stepName
= stepName
.replace("@tableName", tableName
);
167 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, "Marker", SchemaUpdaterBase
.INCLUDE_AUDIT
, !IS_LIST
, IS_1_TO_M
);
170 if (includeIdentifiable
){
173 stepName
= "Add @tableName credits";
174 stepName
= stepName
.replace("@tableName", tableName
);
175 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, null, "Credit", null, null, SchemaUpdaterBase
.INCLUDE_AUDIT
, IS_LIST
, IS_1_TO_M
);
178 stepName
= "Add @tableName identifiers";
179 stepName
= stepName
.replace("@tableName", tableName
);
180 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, null, "Identifier", null, null, SchemaUpdaterBase
.INCLUDE_AUDIT
, IS_LIST
, IS_1_TO_M
);
183 stepName
= "Add @tableName extensions";
184 stepName
= stepName
.replace("@tableName", tableName
);
185 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, "Extension", SchemaUpdaterBase
.INCLUDE_AUDIT
, !IS_LIST
, IS_1_TO_M
);
188 stepName
= "Add @tableName sources";
189 stepName
= stepName
.replace("@tableName", tableName
);
190 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, null, "OriginalSourceBase", null, "sources", SchemaUpdaterBase
.INCLUDE_AUDIT
, ! IS_LIST
, IS_1_TO_M
);
193 stepName
= "Add @tableName rights";
194 stepName
= stepName
.replace("@tableName", tableName
);
195 MnTableCreator
.NewMnInstance(mnTablesStepList
, stepName
, tableName
, null, "RightsInfo", null, "rights", SchemaUpdaterBase
.INCLUDE_AUDIT
, !IS_LIST
, IS_M_TO_M
);
202 protected void invokeOnTable(String tableName
, ICdmDataSource datasource
,
203 IProgressMonitor monitor
, CaseType caseType
, SchemaUpdateResult result
) {
206 String updateQuery
= "CREATE TABLE @tableName (";
209 updateQuery
+= " REV integer not null, revtype " + Datatype
.TINYINTEGER
.format(datasource
, null) + ", ";
212 if (includeCdmBaseAttributes
){
213 String uuidNull
= isAuditing?
"": "NOT NULL";
214 updateQuery
+= " id integer NOT NULL,"
215 + " created " + Datatype
.DATETIME
.format(datasource
, null) + ", "
216 + " uuid varchar(36) "+uuidNull
+","
217 + (excludeVersionableAttributes?
"" : " updated " + Datatype
.DATETIME
.format(datasource
, null) + ", ")
218 + " createdby_id integer,"
219 + (excludeVersionableAttributes ?
"" : " updatedby_id integer, ");
222 if (this.includeEventBase
){
223 updateQuery
+= "timeperiod_start varchar(255), timeperiod_end varchar(255), timeperiod_freetext varchar(255), actor_id int, description varchar(255),";
226 if (this.includeIdentifiableEntity
){
227 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),";
229 if (this.includeSingleSourcedEntity
){
230 updateQuery
+= "source_id integer, ";
233 updateQuery
+= getColumnsSql(tableName
, datasource
, monitor
);
235 //primary and unique keys
236 String primaryKeySql
= primaryKey(isAuditing
)==null ?
"" : "primary key (" + primaryKey(isAuditing
) + "),";
237 String uniqueSql
= unique(isAuditing
)== null ?
"" : "unique(" + unique(isAuditing
) + "),";
238 updateQuery
+= primaryKeySql
+ uniqueSql
;
241 updateQuery
= StringUtils
.chomp(updateQuery
.trim(), ",") + ")";
244 updateQuery
= updateQuery
.replace("@tableName", tableName
);
246 //append datasource specific string
247 updateQuery
+= datasource
.getDatabaseType().getHibernateDialect().getTableTypeString();
248 logger
.debug("UPDATE Query: " + updateQuery
);
251 datasource
.executeUpdate(updateQuery
);
254 createForeignKeys(tableName
, isAuditing
, datasource
, monitor
, caseType
, result
);
257 } catch (Exception e
) {
258 monitor
.warning(e
.getMessage(), e
);
260 result
.addException(e
, e
.getMessage(), "TableCreator.invokeOnTable");
267 * Returns the sql part for the {@link #columnAdders} columns.
268 * This is done by reusing the same method in the ColumnAdder class and removing all the prefixes like 'ADD COLUMN'
270 private String
getColumnsSql(String tableName
, ICdmDataSource datasource
, IProgressMonitor monitor
) throws DatabaseTypeNotSupportedException
{
272 for (ColumnAdder adder
: this.columnAdders
){
273 String singleAdderSQL
= adder
.getUpdateQueryString(tableName
, datasource
, monitor
) + ", ";
275 String
[] split
= singleAdderSQL
.split(ColumnAdder
.getAddColumnSeperator(datasource
));
282 private void createForeignKeys(String tableName
, boolean isAudit
, ICdmDataSource datasource
,
283 IProgressMonitor monitor
, CaseType caseType
, SchemaUpdateResult result
) {
284 if (includeCdmBaseAttributes
){
286 if (! this.excludeVersionableAttributes
){
287 String attribute
= "updatedby";
288 String referencedTable
= "UserAccount";
289 makeForeignKey(tableName
, datasource
, monitor
, attribute
,
290 referencedTable
, caseType
, result
);
294 String attribute
= "createdby";
295 String referencedTable
= "UserAccount";
296 makeForeignKey(tableName
, datasource
, monitor
, attribute
,
297 referencedTable
, caseType
, result
);
302 String attribute
= "REV";
303 String referencedTable
= "AuditEvent";
304 makeForeignKey(tableName
, datasource
, monitor
, attribute
,
305 referencedTable
, caseType
, result
);
307 if (this.includeEventBase
){
309 String attribute
= "actor_id";
310 String referencedTable
= "AgentBase";
311 makeForeignKey(tableName
, datasource
, monitor
, attribute
,
312 referencedTable
, caseType
, result
);
314 if (this.includeSingleSourcedEntity
){
316 String attribute
= "source_id";
317 String referencedTable
= "OriginalSourceBase";
318 makeForeignKey(tableName
, datasource
, monitor
, attribute
,
319 referencedTable
, caseType
, result
);
322 for (ColumnAdder adder
: this.columnAdders
){
323 if (adder
.getReferencedTable() != null){
324 makeForeignKey(tableName
, datasource
, monitor
, adder
.getNewColumnName(),
325 adder
.getReferencedTable(), caseType
, result
);
332 public static void makeForeignKey(String tableName
, ICdmDataSource datasource
,
333 IProgressMonitor monitor
, String attribute
, String referencedTable
, CaseType caseType
,
334 SchemaUpdateResult result
) {
336 referencedTable
= caseType
.transformTo(referencedTable
);
338 String idSuffix
= "_id";
339 if (isRevAttribute(attribute
) || attribute
.endsWith(idSuffix
)){
342 String columnName
= attribute
+ idSuffix
;
344 if (supportsForeignKeys(datasource
, monitor
, tableName
, referencedTable
)){
345 String index
= "FK@tableName_@attribute";
346 index
= index
.replace("@tableName", tableName
);
347 index
= index
.replace("@attribute", attribute
);
350 //OLD - don't remember why we used ADD INDEX here
351 // String updateQuery = "ALTER TABLE @tableName ADD INDEX @index (@attribute), ADD FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
352 String updateQuery
= "ALTER TABLE @tableName ADD @constraintName FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
353 updateQuery
= updateQuery
.replace("@tableName", tableName
);
354 // updateQuery = updateQuery.replace("@index", index);
355 updateQuery
= updateQuery
.replace("@attribute", columnName
);
356 updateQuery
= updateQuery
.replace("@referencedTable", referencedTable
);
357 if (datasource
.getDatabaseType().equals(DatabaseTypeEnum
.MySQL
)){
358 updateQuery
= updateQuery
.replace("@constraintName", "CONSTRAINT " + index
);
360 updateQuery
= updateQuery
.replace("@constraintName", ""); //H2 does not support "CONSTRAINT", didn't check for others
363 if (isRevAttribute(attribute
)){
364 updateQuery
= updateQuery
.replace("@id", "revisionnumber");
366 updateQuery
= updateQuery
.replace("@id", "id");
368 logger
.debug(updateQuery
);
370 datasource
.executeUpdate(updateQuery
);
371 } catch (Exception e
) {
372 String message
= "Problem when creating Foreign Key for " + tableName
+"." + attribute
+": " + e
.getMessage();
373 monitor
.warning(message
);
374 logger
.warn(message
, e
);
375 result
.addWarning(message
);
376 return; //we do not interrupt update if only foreign key generation did not work
381 IndexAdder indexAdder
= IndexAdder
.NewIntegerInstance(null, "Add index instead of Foreign Key", tableName
, columnName
);
383 indexAdder
.invoke(datasource
, monitor
, caseType
, result
);
384 } catch (Exception e
) {
385 String message
= "Problem when creating index instead of Foreign Key for " + tableName
+"." + columnName
+": " + e
.getMessage();
386 monitor
.warning(message
);
387 logger
.warn(message
, e
);
388 result
.addWarning(message
);
389 return; //we do not interrupt update if only index generation did not work
396 * Determines if the tables and the database support foreign keys. If determination is not possible true is returned as default.
398 private static boolean supportsForeignKeys(ICdmDataSource datasource
, IProgressMonitor monitor
, String tableName
, String referencedTable
) {
399 boolean result
= true;
400 if (! datasource
.getDatabaseType().equals(DatabaseTypeEnum
.MySQL
)){
404 String myIsamTables
= "";
405 String format
= "SELECT ENGINE FROM information_schema.TABLES where TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'";
406 String sql
= String
.format(format
, datasource
.getDatabase(), tableName
);
407 String engine
= (String
)datasource
.getSingleValue(sql
);
408 if (engine
.equals("MyISAM")){
410 myIsamTables
= CdmUtils
.concat(",", myIsamTables
, tableName
);
412 sql
= String
.format(format
, datasource
.getDatabase(), referencedTable
);
413 engine
= (String
)datasource
.getSingleValue(sql
);
414 if (engine
.equals("MyISAM")){
416 myIsamTables
= CdmUtils
.concat(",", myIsamTables
, referencedTable
);
418 if (result
== false){
419 String message
= "Tables (%s) use MyISAM engine. MyISAM does not support foreign keys.";
420 message
= String
.format(message
, myIsamTables
);
421 monitor
.warning(message
);
424 } catch (Exception e
) {
425 String message
= "Problems to determine table engine for MySQL.";
426 monitor
.warning(message
);
427 return true; //default
432 private static boolean isRevAttribute(String attribute
) {
433 return "REV".equalsIgnoreCase(attribute
);
438 * Constructs the primary key creation string
442 protected String
primaryKey(boolean isAudit
){
443 String result
= null;
444 if (! isAudit
&& this.primaryKeyParams
!= null){
445 return this.primaryKeyParams
;
446 }else if (isAudit
&& this.primaryKeyParams_AUD
!= null){
447 return this.primaryKeyParams_AUD
;
450 if (includeCdmBaseAttributes
|| ! includeCdmBaseAttributes
){ //TODO how to handle not CDMBase includes => via this.primaryKeyParams
461 * Constructs the unique key creation string
465 protected String
unique(boolean isAudit
){
467 if (this.uniqueParams
!= null){
468 return this.uniqueParams
;
470 if (includeCdmBaseAttributes
){
471 return "uuid"; //TODO how to handle not CDMBase includes
475 if (this.uniqueParams_AUD
!= null){
476 return this.uniqueParams_AUD
;
482 public TableCreator
setPrimaryKeyParams(String primaryKeyParams
, String primaryKeyParams_AUD
) {
483 this.primaryKeyParams
= primaryKeyParams
;
484 this.primaryKeyParams_AUD
= primaryKeyParams_AUD
;
488 public TableCreator
setUniqueParams(String uniqueParams
, String uniqueParams_AUD
) {
489 this.uniqueParams
= uniqueParams
;
490 this.uniqueParams_AUD
= uniqueParams_AUD
;