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
.sql
.ResultSet
;
13 import org
.apache
.commons
.lang3
.StringUtils
;
14 import org
.apache
.logging
.log4j
.LogManager
;
15 import org
.apache
.logging
.log4j
.Logger
;
17 import eu
.etaxonomy
.cdm
.common
.monitor
.DefaultProgressMonitor
;
18 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
19 import eu
.etaxonomy
.cdm
.database
.CdmDataSource
;
20 import eu
.etaxonomy
.cdm
.database
.ICdmDataSource
;
21 import eu
.etaxonomy
.cdm
.database
.update
.v54x_54x
.SchemaUpdater_5431_5440
;
22 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmMetaData
;
25 * This class launches CDM model updates.
27 * For each new schema version number there usually exists 1 {@link ISchemaUpdater} which
28 * represents a list of schema update steps. {@link ISchemaUpdater schema updaters} are linked
29 * to previous updaters which are called, if relevant, previous to the latest updater.
30 * So it is possible to upgrade multiple schema version steps in one call.
32 * As said before each {@link ISchemaUpdater schema updater} creates a list of
33 * {@link ISchemaUpdaterStep update steps}.
35 * {@link ISchemaUpdater} support progression monitoring with each update step being one task.
37 * ATTENTION: Some steps in the schema update are not transactional by nature. E.g. adding or removing a column
38 * to a table in a SQL database can not be handled in a transaction. Therefore failures in
39 * certain steps may not lead to a complete rollback of all steps covered by a {@link ISchemaUpdater}.
40 * This may lead to a situation where the database becomes inconsistent.
42 * <u>HOW TO ADD A NEW UPDATER?</u><BR>
43 * Adding a new updater currently still needs adjustment at multiple places.
45 * <BR>1.) Increment {@link CdmMetaData} schema version number.
46 * <BR>2.) Create a new class instance of {@link SchemaUpdaterBase} (e.g. by copying an old one).
47 * <BR>3.) Update startSchemaVersion and endSchemaVersion in this new class, where startSchemaVersion
48 * is the old schema version and endSchemaVersion is the new schema version.
49 * <BR>4.) Implement {@link ISchemaUpdater#getPreviousUpdater()} and {@link ISchemaUpdater#getNextUpdater()}
50 * in a way that the former returns an instance of the previous schema updater and the later returns null (for now).
51 * <BR>5.) Go to the previous schema updater class and adjust {@link ISchemaUpdater#getNextUpdater()}
52 * in a way that it returns an instance of the newly created updater.
53 * <BR>6.) Adjust {@link CdmUpdater#getCurrentSchemaUpdater()} to return
54 * instances of the newly created updater.
56 * NOTE: Prior to cdmlib version 4.8/schema version 4.7 the CdmUpdater was split into a schema updater
57 * and a term updater. This architecture caused problems and was therefore removed in 4.8.
60 * @see ISchemaUpdaterStep
65 public class CdmUpdater
{
67 private static final Logger logger
= LogManager
.getLogger();
69 private static final ISchemaUpdater
getCurrentSchemaUpdater() {
70 return SchemaUpdater_5431_5440
.NewInstance();
73 public static CdmUpdater
NewInstance(){
74 return new CdmUpdater();
77 public SchemaUpdateResult
updateToCurrentVersion(ICdmDataSource datasource
, IProgressMonitor monitor
){
78 SchemaUpdateResult result
= new SchemaUpdateResult();
80 monitor
= DefaultProgressMonitor
.NewInstance();
82 CaseType caseType
= CaseType
.caseTypeOfDatasource(datasource
);
84 ISchemaUpdater currentSchemaUpdater
= getCurrentSchemaUpdater();
86 int steps
= currentSchemaUpdater
.countSteps(datasource
, monitor
, caseType
);
87 steps
++; //for hibernate_sequences update
89 String taskName
= "Update to schema version " + currentSchemaUpdater
.getTargetVersion();
90 monitor
.beginTask(taskName
, steps
);
93 datasource
.startTransaction();
94 currentSchemaUpdater
.invoke(datasource
, monitor
, caseType
, result
);
95 if (result
.isSuccess()){
96 //TODO should not run if no update was necesssary
97 updateHibernateSequence(datasource
, monitor
, caseType
, result
);
99 if (!result
.isSuccess()){
100 datasource
.rollback(); //does not work for ddl statements, therefore not really necessary
102 datasource
.commitTransaction();
104 } catch (Exception e
) {
105 String message
= "Stopped schema updater";
106 result
.addException(e
, message
, "CdmUpdater");
107 monitor
.warning(message
);
109 String message
= "Update finished " + (result
.isSuccess() ?
"successfully" : "with ERRORS");
110 monitor
.subTask(message
);
111 if (!result
.isSuccess()){
112 monitor
.warning(message
);
113 monitor
.setCanceled(true);
117 logger
.info(message
);
124 * Updating terms often inserts new terms, vocabularies and representations.
125 * Therefore the counter in hibernate_sequences must be increased.
126 * We do this once at the end of term updating.
129 * @return true if update was successful, false otherwise
131 private void updateHibernateSequence(ICdmDataSource datasource
, IProgressMonitor monitor
,
132 CaseType caseType
, SchemaUpdateResult result
) {
133 monitor
.subTask("Update hibernate sequences");
135 String sql
= "SELECT * FROM hibernate_sequences ";
136 ResultSet rs
= datasource
.executeQuery(sql
);
138 String table
= rs
.getString("sequence_name");
139 Integer val
= rs
.getInt("next_val");
140 updateSingleValue(datasource
, monitor
, table
, val
, caseType
, result
);
142 } catch (Exception e
) {
143 String message
= "Exception occurred when trying to update hibernate_sequences table: " + e
.getMessage();
144 monitor
.warning(message
, e
);
145 logger
.error(message
);
146 result
.addException(e
, message
, "CdmUpdater.updateHibernateSequence");
153 private void updateSingleValue(ICdmDataSource datasource
, IProgressMonitor monitor
, String table
,
154 Integer oldVal
, CaseType caseType
, SchemaUpdateResult result
){
155 if (table
.equals("default")){ //found in flora central africa test database
161 String id
= table
.equalsIgnoreCase("AuditEvent")?
"revisionNumber" : "id";
162 String sql
= " SELECT max(%s) FROM %s ";
163 newVal
= (Integer
)datasource
.getSingleValue(String
.format(sql
, id
, caseType
.transformTo(table
)));
164 } catch (Exception e
) {
165 String message
= "Could not retrieve max value for table '%s'. Will not update hibernate_sequence for this table. " +
166 "Usually this will not cause problems, however, if new data has been added to " +
167 "this table by the update script one may encounter 'unique identifier' " +
168 "exceptions when trying to add further data.";
169 monitor
.warning(String
.format(message
,table
), e
);
170 result
.addWarning(message
, (String
)null, "table = " + table
);
175 //This is how {@link PooledOptimizer#generate(org.hibernate.id.enhanced.AccessCallback)} works
176 //it substracts the increment size from the value in hibernate_sequences to get the initial value.
177 //Haven't checked why.
178 //For the correct increment size see eu.etaxonomy.cdm.model.common.package-info.java
179 int incrementSize
= 10;
180 newVal
= newVal
+ incrementSize
;
181 if (newVal
>= oldVal
){
182 String sql
= " UPDATE hibernate_sequences " +
183 " SET next_val = %d " +
184 " WHERE sequence_name = '%s' ";
185 datasource
.executeUpdate(String
.format(sql
, newVal
+ 1 , table
) );
189 } catch (Exception e
) {
190 String message
= "Exception occurred when trying to read or update hibernate_sequences table for value " + table
+ ": " + e
.getMessage();
191 monitor
.warning(message
, e
);
192 logger
.error(message
);
193 result
.addException(e
, message
, "CdmUpdater.updateSingleValue(table = " + table
+ ")");
198 * @param args SERVER DB_NAME1[,DB_NAME2,...] [USER] [PASSWORD] [PORT]
200 public static void main(String
[] args
) {
201 // logger.warn("main method not yet fully implemented (only works with mysql!!!)");
202 // if(args.length < 2){
203 // logger.error("Arguments missing: server database [username [password]]");
205 //TODO better implementation
206 CdmUpdater myUpdater
= new CdmUpdater();
207 System
.out
.println("CdmUpdater\nArguments: SERVER DB_NAME1[,DB_NAME2,...] [USER] [PASSWORD] [PORT]");
208 String server
= args
[0];
209 String database
= args
[1];
210 String
[] databaseNames
= StringUtils
.split(database
, ',');
211 String username
= args
.length
> 2 ? args
[2] : null;
212 String password
= args
.length
> 3 ? args
[3] : null;
214 if( args
.length
> 4){
216 port
= Integer
.parseInt(args
[4]);
217 } catch (Exception e
) {
221 System
.out
.println("Number of databases to update: " + databaseNames
.length
);
222 for(String dnName
: databaseNames
){
223 System
.out
.println(dnName
+ " UPDATE ...");
224 ICdmDataSource dataSource
= CdmDataSource
.NewMySqlInstance(server
, dnName
, port
, username
, password
);
225 SchemaUpdateResult result
= myUpdater
.updateToCurrentVersion(dataSource
, null);
226 System
.out
.println(dnName
+ " DONE " + (result
.isSuccess() ?
"successfully" : "with ERRORS"));
227 System
.out
.println(result
.createReport().toString());
228 System
.out
.println("====================================================================");