3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.database
.update
;
12 import java
.sql
.SQLException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Arrays
;
15 import java
.util
.List
;
17 import org
.apache
.commons
.lang
.StringUtils
;
18 import org
.apache
.log4j
.Logger
;
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
;
31 public class TableCreator
extends AuditedSchemaUpdaterStepBase
<TableCreator
> implements ISchemaUpdaterStep
{
32 private static final Logger logger
= Logger
.getLogger(TableCreator
.class);
34 private static final boolean SORT_INDEX
= true;
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
;
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);
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);
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);
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;
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);
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
) {
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
;
95 public List
<ISchemaUpdaterStep
> getInnerSteps() {
96 return mnTablesStepList
;
100 * Fills the {@link #columnAdders} list.
102 private void makeColumnAdders() {
103 if (columnNames
.size() != columnTypes
.size()){
104 throw new RuntimeException ("ColumnNames and columnTypes must be of same size. Step: " + getStepName());
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
);
132 * fills the mnTablesStepList
134 private void makeMnTables() {
135 TableCreator tableCreator
;
137 if (this.includeAnnotatableEntity
){
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
);
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
);
152 if (this.includeIdentifiableEntity
){
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
);
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
);
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
);
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
);
182 protected boolean invokeOnTable(String tableName
, ICdmDataSource datasource
, IProgressMonitor monitor
) {
184 boolean result
= true;
186 String updateQuery
= "CREATE TABLE @tableName (";
189 updateQuery
+= " REV integer not null, revtype tinyint, ";
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, ";
201 if (this.includeEventBase
){
202 updateQuery
+= "timeperiod_start varchar(255), timeperiod_end varchar(255), timeperiod_freetext varchar(255), actor_id int, description varchar(255),";
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),";
209 updateQuery
+= getColumnsSql(tableName
, datasource
, monitor
);
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
;
217 updateQuery
= StringUtils
.chomp(updateQuery
.trim(), ",") + ")";
220 updateQuery
= updateQuery
.replace("@tableName", tableName
);
222 //append datasource specific string
223 updateQuery
+= datasource
.getDatabaseType().getHibernateDialect().getTableTypeString();
224 logger
.debug("UPDATE Query: " + updateQuery
);
227 datasource
.executeUpdate(updateQuery
);
230 result
&= createForeignKeys(tableName
, isAuditing
, datasource
, monitor
);
233 } catch (Exception e
) {
234 monitor
.warning(e
.getMessage(), e
);
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'
245 private String
getColumnsSql(String tableName
, ICdmDataSource datasource
, IProgressMonitor monitor
) throws DatabaseTypeNotSupportedException
{
247 for (ColumnAdder adder
: this.columnAdders
){
248 String singleAdderSQL
= adder
.getUpdateQueryString(tableName
, datasource
, monitor
) + ", ";
250 String
[] split
= singleAdderSQL
.split(ColumnAdder
.getAddColumnSeperator(datasource
));
257 private boolean createForeignKeys(String tableName
, boolean isAudit
, ICdmDataSource datasource
, IProgressMonitor monitor
) 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
);
264 attribute
= "createdby";
265 referencedTable
= "UserAccount";
266 result
&= makeForeignKey(tableName
, datasource
, monitor
, attribute
, referencedTable
);
270 String attribute
= "REV";
271 String referencedTable
= "AuditEvent";
272 result
&= makeForeignKey(tableName
, datasource
, monitor
, attribute
, referencedTable
);
274 if (this.includeEventBase
){
275 String attribute
= "actor_id";
276 String referencedTable
= "AgentBase";
277 result
&= makeForeignKey(tableName
, datasource
, monitor
, attribute
, referencedTable
);
279 for (ColumnAdder adder
: this.columnAdders
){
280 if (adder
.getReferencedTable() != null){
281 result
&= makeForeignKey(tableName
, datasource
, monitor
, adder
.getNewColumnName(), adder
.getReferencedTable());
287 public static boolean makeForeignKey(String tableName
, ICdmDataSource datasource
, IProgressMonitor monitor
, String attribute
, String referencedTable
) throws SQLException
{
288 boolean result
= true;
290 if (supportsForeignKeys(datasource
, monitor
, tableName
, referencedTable
)){
291 String index
= "FK@tableName_@attribute";
292 index
= index
.replace("@tableName", tableName
);
293 index
= index
.replace("@attribute", attribute
);
295 String idSuffix
= "_id";
296 if (isRevAttribute(attribute
) || attribute
.endsWith(idSuffix
)){
299 String updateQuery
= "ALTER TABLE @tableName ADD INDEX @index (@attribute), ADD CONSTRAINT @index FOREIGN KEY (@attribute) REFERENCES @referencedTable (@id)";
300 updateQuery
= updateQuery
.replace("@tableName", tableName
);
301 updateQuery
= updateQuery
.replace("@index", index
);
302 updateQuery
= updateQuery
.replace("@attribute", attribute
+ idSuffix
);
303 updateQuery
= updateQuery
.replace("@referencedTable", referencedTable
);
304 if (isRevAttribute(attribute
)){
305 updateQuery
= updateQuery
.replace("@id", "revisionnumber");
307 updateQuery
= updateQuery
.replace("@id", "id");
309 logger
.debug(updateQuery
);
311 datasource
.executeUpdate(updateQuery
);
312 } catch (Exception e
) {
313 String message
= "Problem when creating Foreign Key for " + tableName
+"." + attribute
+": " + e
.getMessage();
314 monitor
.warning(message
);
315 logger
.warn(message
, e
);
326 * Determines if the tables and the database support foreign keys. If determination is not possible true is returned as default.
330 * @param referencedTable
333 private static boolean supportsForeignKeys(ICdmDataSource datasource
, IProgressMonitor monitor
, String tableName
, String referencedTable
) {
334 boolean result
= true;
335 if (! datasource
.getDatabaseType().equals(DatabaseTypeEnum
.MySQL
)){
339 String myIsamTables
= "";
340 String format
= "SELECT ENGINE FROM information_schema.TABLES where TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'";
341 String sql
= String
.format(format
, datasource
.getDatabase(), tableName
);
342 String engine
= (String
)datasource
.getSingleValue(sql
);
343 if (engine
.equals("MyISAM")){
345 myIsamTables
= CdmUtils
.concat(",", myIsamTables
, tableName
);
347 sql
= String
.format(format
, datasource
.getDatabase(), referencedTable
);
348 engine
= (String
)datasource
.getSingleValue(sql
);
349 if (engine
.equals("MyISAM")){
351 myIsamTables
= CdmUtils
.concat(",", myIsamTables
, referencedTable
);
353 if (result
== false){
354 String message
= "Tables (%s) use MyISAM engine. MyISAM does not support foreign keys.";
355 message
= String
.format(message
, myIsamTables
);
356 monitor
.warning(message
);
359 } catch (Exception e
) {
360 String message
= "Problems to determine table engine for MySQL.";
361 monitor
.warning(message
);
362 return true; //default
370 private static boolean isRevAttribute(String attribute
) {
371 return "REV".equalsIgnoreCase(attribute
);
376 * Constructs the primary key creation string
380 protected String
primaryKey(boolean isAudit
){
381 String result
= null;
382 if (! isAudit
&& this.primaryKeyParams
!= null){
383 return this.primaryKeyParams
;
384 }else if (isAudit
&& this.primaryKeyParams_AUD
!= null){
385 return this.primaryKeyParams_AUD
;
388 if (includeCdmBaseAttributes
|| ! includeCdmBaseAttributes
){ //TODO how to handle not CDMBase includes
399 * Constructs the unique key creation string
403 protected String
unique(boolean isAudit
){
405 if (this.uniqueParams
!= null){
406 return this.uniqueParams
;
408 if (includeCdmBaseAttributes
){
409 return "uuid"; //TODO how to handle not CDMBase includes
413 if (this.uniqueParams_AUD
!= null){
414 return this.uniqueParams_AUD
;
420 public void setPrimaryKeyParams(String primaryKeyParams
, String primaryKeyParams_AUD
) {
421 this.primaryKeyParams
= primaryKeyParams
;
422 this.primaryKeyParams_AUD
= primaryKeyParams_AUD
;
425 public void setUniqueParams(String uniqueParams
, String uniqueParams_AUD
) {
426 this.uniqueParams
= uniqueParams
;
427 this.uniqueParams_AUD
= uniqueParams_AUD
;