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
.ResultSet
;
14 import org
.apache
.log4j
.Logger
;
16 import eu
.etaxonomy
.cdm
.common
.monitor
.DefaultProgressMonitor
;
17 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
18 import eu
.etaxonomy
.cdm
.database
.CdmDataSource
;
19 import eu
.etaxonomy
.cdm
.database
.ICdmDataSource
;
20 import eu
.etaxonomy
.cdm
.database
.update
.v33_34
.SchemaUpdater_331_34
;
21 import eu
.etaxonomy
.cdm
.database
.update
.v33_34
.TermUpdater_33_34
;
22 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmMetaData
;
25 * This class launches CDM model updates.
26 * Currently it splits the update in model updates and defined term related updates by
27 * using a {@link ISchemaUpdater schema updater} and a {@link ITermUpdater}. However, this
28 * architecture often results in problems and therefore will be replaced by only 1 schema updater.
29 * Term updates will be handled differently or also by using the schema updater.
31 * For each new schema version number there usually exists 1 {@link ISchemaUpdater} which is
32 * represents a list of schema update steps. {@link ISchemaUpdater schema updaters} are linked
33 * to previous updaters which are called, if relevant, previous to the latest updater.
34 * So it is possible to upgrade multiple schema version steps in one call.
36 * As said before each {@link ISchemaUpdater schema updater} creates a list of
37 * {@link ISchemaUpdaterStep update steps}.
39 * {@link ISchemaUpdater} support progression monitoring with each update step being one task.
41 * ATTENTION: Some steps in the schema update are not transactional by nature. E.g. adding or removing a column
42 * to a table in a SQL database can not be handled in a transaction. Therefore failures in
43 * certain steps may not lead to a complete rollback of all steps covered by a {@link ISchemaUpdater}.
44 * This may lead to a situation where the database becomes inconsistent.
46 * <u>HOW TO ADD A NEW UPDATER?</u><BR>
47 * Adding a new updater currently still needs adjustment at multiple places.
49 * <BR>1.) Increment {@link CdmMetaData} schema version number and term version number.
50 * <BR>2.) Create a new class instance of {@link SchemaUpdaterBase} (e.g. by copying an old one).
51 * <BR>3.) Update startSchemaVersion and endSchemaVersion in this new class, where startSchemaVersion
52 * is the old schema version and endSchemaVersion is the new schema version.
53 * <BR>4.) Implement {@link ISchemaUpdater#getPreviousUpdater()} and {@link ISchemaUpdater#getNextUpdater()}
54 * in a way that the former returns an instance of the previous schema updater and the later returns null (for now).
55 * <BR>5.) Go to the previous schema updater class and adjust {@link ISchemaUpdater#getNextUpdater()}
56 * in a way that it returns an instance of the newly created updater.
57 * <BR>6.) Repeat steps 2.-5. for {@link ITermUpdater}
58 * <BR>7.) Adjust {@link #getCurrentSchemaUpdater()} and {@link #getCurrentTermUpdater()} to return
59 * instances of the newly created updaters.
63 * @see ISchemaUpdaterStep
64 * @see ITermUpdaterStep
70 public class CdmUpdater
{
71 private static final Logger logger
= Logger
.getLogger(CdmUpdater
.class);
73 public static CdmUpdater
NewInstance(){
74 return new CdmUpdater();
79 * @param monitor may be <code>null</code>
82 public boolean updateToCurrentVersion(ICdmDataSource datasource
, IProgressMonitor monitor
){
83 boolean result
= true;
85 monitor
= DefaultProgressMonitor
.NewInstance();
87 CaseType caseType
= CaseType
.caseTypeOfDatasource(datasource
);
89 ISchemaUpdater currentSchemaUpdater
= getCurrentSchemaUpdater();
90 // TODO do we really always update the terms??
91 ITermUpdater currentTermUpdater
= getCurrentTermUpdater();
93 int steps
= currentSchemaUpdater
.countSteps(datasource
, monitor
, caseType
);
94 steps
+= currentTermUpdater
.countSteps(datasource
, monitor
, caseType
);
95 steps
++; //for hibernate_sequences update
97 String taskName
= "Update to schema version " + currentSchemaUpdater
.getTargetVersion() + " and to term version " + currentTermUpdater
.getTargetVersion(); //+ currentSchemaUpdater.getVersion();
98 monitor
.beginTask(taskName
, steps
);
101 datasource
.startTransaction();
102 result
&= currentSchemaUpdater
.invoke(datasource
, monitor
, caseType
);
104 result
&= currentTermUpdater
.invoke(datasource
, monitor
, caseType
);
105 updateHibernateSequence(datasource
, monitor
, caseType
);
107 if (result
== false){
108 datasource
.rollback();
110 datasource
.commitTransaction();
113 } catch (Exception e
) {
115 monitor
.warning("Stopped schema updater");
117 String message
= "Update finished " + (result ?
"successfully" : "with ERRORS");
118 monitor
.subTask(message
);
120 monitor
.warning(message
);
121 monitor
.setCanceled(true);
125 logger
.info(message
);
134 * Updating terms often inserts new terms, vocabularies and representations.
135 * Therefore the counter in hibernate_sequences must be increased.
136 * We do this once at the end of term updating.
138 * @return true if update was successful, false otherwise
140 private boolean updateHibernateSequence(ICdmDataSource datasource
, IProgressMonitor monitor
, CaseType caseType
) {
141 boolean result
= true;
142 monitor
.subTask("Update hibernate sequences");
144 String sql
= "SELECT * FROM hibernate_sequences ";
145 ResultSet rs
= datasource
.executeQuery(sql
);
147 String table
= rs
.getString("sequence_name");
148 Integer val
= rs
.getInt("next_val");
149 result
&= updateSingleValue(datasource
,monitor
, table
, val
, caseType
);
151 } catch (Exception e
) {
152 String message
= "Exception occurred when trying to update hibernate_sequences table: " + e
.getMessage();
153 monitor
.warning(message
, e
);
154 logger
.error(message
);
171 private boolean updateSingleValue(ICdmDataSource datasource
, IProgressMonitor monitor
, String table
, Integer oldVal
, CaseType caseType
){
172 if (table
.equals("default")){ //found in flora central africa test database
178 String sql
= " SELECT max(id) FROM %s ";
179 newVal
= (Integer
)datasource
.getSingleValue(String
.format(sql
, caseType
.transformTo(table
)));
180 } catch (Exception e
) {
181 String message
= "Could not retrieve max value for table '%s'. Will not update hibernate_sequence for this table. " +
182 "Usually this will not cause problems, however, if new data has been added to " +
183 "this table by the update script one may encounter 'unique identifier' " +
184 "exceptions when trying to add further data.";
185 monitor
.warning(String
.format(message
,table
), e
);
191 //This is how {@link PooledOptimizer#generate(org.hibernate.id.enhanced.AccessCallback)} works
192 //it substracts the increment size from the value in hibernate_sequences to get the initial value.
193 //Haven't checked why.
194 //For the correct increment size see eu.etaxonomy.cdm.model.common.package-info.java
195 int incrementSize
= 10;
196 newVal
= newVal
+ incrementSize
;
197 if (newVal
!= null && newVal
>= oldVal
){
198 String sql
= " UPDATE hibernate_sequences " +
199 " SET next_val = %d " +
200 " WHERE sequence_name = '%s' ";
201 datasource
.executeUpdate(String
.format(sql
, newVal
+ 1 , table
) );
205 } catch (Exception e
) {
206 String message
= "Exception occurred when trying to read or update hibernate_sequences table for value " + table
+ ": " + e
.getMessage();
207 monitor
.warning(message
, e
);
208 logger
.error(message
);
215 private ITermUpdater
getCurrentTermUpdater() {
216 return TermUpdater_33_34
.NewInstance();
220 * Returns the current CDM updater
223 private ISchemaUpdater
getCurrentSchemaUpdater() {
224 return SchemaUpdater_331_34
.NewInstance();
230 public static void main(String
[] args
) {
231 // logger.warn("main method not yet fully implemented (only works with mysql!!!)");
232 // if(args.length < 2){
233 // logger.error("Arguments missing: server database [username [password]]");
235 //TODO better implementation
236 CdmUpdater myUpdater
= new CdmUpdater();
237 String server
= args
[0];
238 String database
= args
[1];
239 String username
= args
.length
> 2 ? args
[2] : null;
240 String password
= args
.length
> 3 ? args
[3] : null;
242 if( args
.length
> 4){
244 port
= Integer
.parseInt(args
[4]);
245 } catch (Exception e
) {
250 ICdmDataSource dataSource
= CdmDataSource
.NewMySqlInstance(server
, database
, port
, username
, password
, null);
251 boolean success
= myUpdater
.updateToCurrentVersion(dataSource
, null);
252 System
.out
.println("DONE " + (success ?
"successfully" : "with ERRORS"));