Project

General

Profile

« Previous | Next » 

Revision c47d1771

Added by Andreas Müller about 8 years ago

Handle AuditEvent hibernate sequence update in CdmUpdater correctly
#4716

View differences:

cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/database/update/CdmUpdater.java
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.ResultSet;
13

  
14
import org.apache.commons.lang.StringUtils;
15
import org.apache.log4j.Logger;
16

  
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.v35_36.SchemaUpdater_35_36;
22
import eu.etaxonomy.cdm.database.update.v35_36.TermUpdater_35_36;
23
import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
24

  
25
/**
26
 * This class launches CDM model updates.
27
 * Currently it splits the update in model updates and defined term related updates by
28
 * using a {@link ISchemaUpdater schema updater} and a {@link ITermUpdater}. However, this
29
 * architecture often results in problems and therefore will be replaced by only 1 schema updater.
30
 * Term updates will be handled differently or also by using the schema updater.
31
 * <BR>
32
 * For each new schema version number there usually exists 1 {@link ISchemaUpdater} which is
33
 * represents a list of schema update steps. {@link ISchemaUpdater schema updaters} are linked
34
 * to previous updaters which are called, if relevant, previous to the latest updater.
35
 * So it is possible to upgrade multiple schema version steps in one call.
36
 * <BR><BR>
37
 * As said before each {@link ISchemaUpdater schema updater} creates a list of
38
 * {@link ISchemaUpdaterStep update steps}.
39
 * <BR><BR>
40
 * {@link ISchemaUpdater} support progression monitoring with each update step being one task.
41
 * <BR><BR>
42
 * ATTENTION: Some steps in the schema update are not transactional by nature. E.g. adding or removing a column
43
 * to a table in a SQL database can not be handled in a transaction. Therefore failures in
44
 * certain steps may not lead to a complete rollback of all steps covered by a {@link ISchemaUpdater}.
45
 * This may lead to a situation where the database becomes inconsistent.
46
 * <BR><BR>
47
 * <u>HOW TO ADD A NEW UPDATER?</u><BR>
48
 * Adding a new updater currently still needs adjustment at multiple places.
49
 * <BR>
50
 * <BR>1.) Increment {@link CdmMetaData} schema version number and term version number.
51
 * <BR>2.) Create a new class instance of {@link SchemaUpdaterBase} (e.g. by copying an old one).
52
 * <BR>3.) Update startSchemaVersion and endSchemaVersion in this new class, where startSchemaVersion
53
 * is the old schema version and endSchemaVersion is the new schema version.
54
 * <BR>4.) Implement {@link ISchemaUpdater#getPreviousUpdater()} and {@link ISchemaUpdater#getNextUpdater()}
55
 * in a way that the former returns an instance of the previous schema updater and the later returns null (for now).
56
 * <BR>5.) Go to the previous schema updater class and adjust {@link ISchemaUpdater#getNextUpdater()}
57
 * in a way that it returns an instance of the newly created updater.
58
 * <BR>6.) Repeat steps 2.-5. for {@link ITermUpdater}
59
 * <BR>7.) Adjust {@link #getCurrentSchemaUpdater()} and {@link #getCurrentTermUpdater()} to return
60
 * instances of the newly created updaters.
61
 *
62
 * @see ISchemaUpdater
63
 * @see ITermUpdater
64
 * @see ISchemaUpdaterStep
65
 * @see ITermUpdaterStep
66
 *
67
 * @author a.mueller
68
 * @date 10.09.2010
69
 *
70
 */
71
public class CdmUpdater {
72
    private static final Logger logger = Logger.getLogger(CdmUpdater.class);
73

  
74
    public static CdmUpdater NewInstance(){
75
        return new CdmUpdater();
76
    }
77
    
78

  
79
    private ITermUpdater getCurrentTermUpdater() {
80
        return TermUpdater_35_36.NewInstance();
81
    }
82

  
83
    /**
84
     * Returns the current CDM updater
85
     * @return
86
     */
87
    private ISchemaUpdater getCurrentSchemaUpdater() {
88
        return SchemaUpdater_35_36.NewInstance();
89
    }
90

  
91
    /**
92
     * @param datasource
93
     * @param monitor may be <code>null</code>
94
     * @return
95
     */
96
    public boolean updateToCurrentVersion(ICdmDataSource datasource, IProgressMonitor monitor){
97
        boolean result = true;
98
        if (monitor == null){
99
            monitor = DefaultProgressMonitor.NewInstance();
100
        }
101
        CaseType caseType = CaseType.caseTypeOfDatasource(datasource);
102

  
103
        ISchemaUpdater currentSchemaUpdater = getCurrentSchemaUpdater();
104
        // TODO do we really always update the terms??
105
        ITermUpdater currentTermUpdater = getCurrentTermUpdater();
106

  
107
        int steps = currentSchemaUpdater.countSteps(datasource, monitor, caseType);
108
        steps += currentTermUpdater.countSteps(datasource, monitor, caseType);
109
        steps++;  //for hibernate_sequences update
110

  
111
        String taskName = "Update to schema version " + currentSchemaUpdater.getTargetVersion() + " and to term version " + currentTermUpdater.getTargetVersion(); //+ currentSchemaUpdater.getVersion();
112
        monitor.beginTask(taskName, steps);
113

  
114
        try {
115
            datasource.startTransaction();
116
            result &= currentSchemaUpdater.invoke(datasource, monitor, caseType);
117
            if (result == true){
118
                result &= currentTermUpdater.invoke(datasource, monitor, caseType);
119
                updateHibernateSequence(datasource, monitor, caseType);
120
            }
121
            if (result == false){
122
                datasource.rollback();
123
            }else{
124
                datasource.commitTransaction();
125
            }
126

  
127
        } catch (Exception e) {
128
            result = false;
129
            monitor.warning("Stopped schema updater");
130
        } finally {
131
            String message = "Update finished " + (result ? "successfully" : "with ERRORS");
132
            monitor.subTask(message);
133
            if (!result){
134
                monitor.warning(message);
135
                monitor.setCanceled(true);
136
            }else{
137
                monitor.done();
138
            }
139
            logger.info(message);
140
        }
141

  
142
        return result;
143
    }
144

  
145

  
146

  
147
    /**
148
     * Updating terms often inserts new terms, vocabularies and representations.
149
     * Therefore the counter in hibernate_sequences must be increased.
150
     * We do this once at the end of term updating.
151
     * @param caseType
152
     * @return true if update was successful, false otherwise
153
     */
154
    private boolean updateHibernateSequence(ICdmDataSource datasource, IProgressMonitor monitor, CaseType caseType) {
155
        boolean result = true;
156
        monitor.subTask("Update hibernate sequences");
157
        try {
158
            String sql = "SELECT * FROM hibernate_sequences ";
159
            ResultSet rs = datasource.executeQuery(sql);
160
            while (rs.next()){
161
                String table = rs.getString("sequence_name");
162
                Integer val = rs.getInt("next_val");
163
                result &= updateSingleValue(datasource,monitor, table, val, caseType);
164
            }
165
        } catch (Exception e) {
166
            String message = "Exception occurred when trying to update hibernate_sequences table: " + e.getMessage();
167
            monitor.warning(message, e);
168
            logger.error(message);
169
            result = false;
170
        }finally{
171
            monitor.worked(1);
172
        }
173
        return result;
174
    }
175

  
176
    /**
177
     *
178
     * @param datasource
179
     * @param monitor
180
     * @param table
181
     * @param oldVal
182
     * @param caseType
183
     * @return
184
     */
185
    private boolean updateSingleValue(ICdmDataSource datasource, IProgressMonitor monitor, String table, Integer oldVal, CaseType caseType){
186
        if (table.equals("default")){  //found in flora central africa test database
187
            return true;
188
        }
189
        try {
190
            Integer newVal;
191
            try {
192
                String sql = " SELECT max(id) FROM %s ";
193
                newVal = (Integer)datasource.getSingleValue(String.format(sql, caseType.transformTo(table)));
194
            } catch (Exception e) {
195
                String message = "Could not retrieve max value for table '%s'. Will not update hibernate_sequence for this table. " +
196
                        "Usually this will not cause problems, however, if new data has been added to " +
197
                        "this table by the update script one may encounter 'unique identifier' " +
198
                        "exceptions when trying to add further data.";
199
                monitor.warning(String.format(message,table), e);
200
                //TODO
201
                return true;
202
            }
203

  
204
            if (newVal != null){
205
                //This is how {@link PooledOptimizer#generate(org.hibernate.id.enhanced.AccessCallback)} works
206
                //it substracts the increment size from the value in hibernate_sequences to get the initial value.
207
                //Haven't checked why.
208
                //For the correct increment size see eu.etaxonomy.cdm.model.common.package-info.java
209
                int incrementSize = 10;
210
                newVal = newVal + incrementSize;
211
                if (newVal != null && newVal >= oldVal){
212
                    String sql = " UPDATE hibernate_sequences " +
213
                            " SET next_val = %d " +
214
                            " WHERE sequence_name = '%s' ";
215
                    datasource.executeUpdate(String.format(sql, newVal + 1 , table) );
216
                }
217
            }
218
            return true;
219
        } catch (Exception e) {
220
            String message = "Exception occurred when trying to read or update hibernate_sequences table for value " + table + ": " + e.getMessage();
221
            monitor.warning(message, e);
222
            logger.error(message);
223
            return false;
224
        }
225

  
226
    }
227

  
228

  
229

  
230
    /**
231
     *
232
     *
233
     * @param args SERVER DB_NAME1[,DB_NAME2,...] [USER] [PASSWORD] [PORT]
234
     */
235
    public static void main(String[] args) {
236
//        logger.warn("main method not yet fully implemented (only works with mysql!!!)");
237
//        if(args.length < 2){
238
//            logger.error("Arguments missing: server database [username [password]]");
239
//        }
240
        //TODO better implementation
241
        CdmUpdater myUpdater = new CdmUpdater();
242
        System.out.println("CdmUpdater\nArguments: SERVER DB_NAME1[,DB_NAME2,...] [USER] [PASSWORD] [PORT]");
243
        String server = args[0];
244
        String database  = args[1];
245
        String[] databaseNames = StringUtils.split(database, ',');
246
        String username = args.length > 2 ? args[2] : null;
247
        String password  = args.length > 3 ? args[3] : null;
248
        int port  = 3306;
249
        if( args.length > 4){
250
            try {
251
                port = Integer.parseInt(args[4]);
252
            } catch (Exception e) {
253
                // ignore
254
            }
255
        }
256
        System.out.println("Number of databases to update: " + databaseNames.length);
257
        for(String dnName : databaseNames){
258
            System.out.println(dnName + " UPDATE ...");
259
            ICdmDataSource dataSource = CdmDataSource.NewMySqlInstance(server, dnName, port, username, password, null);
260
            boolean success = myUpdater.updateToCurrentVersion(dataSource, null);
261
            System.out.println(dnName + " DONE " + (success ? "successfully" : "with ERRORS"));
262
            System.out.println("====================================================================");
263

  
264
        }
265
    }
266

  
267
}
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.ResultSet;
13

  
14
import org.apache.commons.lang.StringUtils;
15
import org.apache.log4j.Logger;
16

  
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.v35_36.SchemaUpdater_35_36;
22
import eu.etaxonomy.cdm.database.update.v35_36.TermUpdater_35_36;
23
import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
24

  
25
/**
26
 * This class launches CDM model updates.
27
 * Currently it splits the update in model updates and defined term related updates by
28
 * using a {@link ISchemaUpdater schema updater} and a {@link ITermUpdater}. However, this
29
 * architecture often results in problems and therefore will be replaced by only 1 schema updater.
30
 * Term updates will be handled differently or also by using the schema updater.
31
 * <BR>
32
 * For each new schema version number there usually exists 1 {@link ISchemaUpdater} which is
33
 * represents a list of schema update steps. {@link ISchemaUpdater schema updaters} are linked
34
 * to previous updaters which are called, if relevant, previous to the latest updater.
35
 * So it is possible to upgrade multiple schema version steps in one call.
36
 * <BR><BR>
37
 * As said before each {@link ISchemaUpdater schema updater} creates a list of
38
 * {@link ISchemaUpdaterStep update steps}.
39
 * <BR><BR>
40
 * {@link ISchemaUpdater} support progression monitoring with each update step being one task.
41
 * <BR><BR>
42
 * ATTENTION: Some steps in the schema update are not transactional by nature. E.g. adding or removing a column
43
 * to a table in a SQL database can not be handled in a transaction. Therefore failures in
44
 * certain steps may not lead to a complete rollback of all steps covered by a {@link ISchemaUpdater}.
45
 * This may lead to a situation where the database becomes inconsistent.
46
 * <BR><BR>
47
 * <u>HOW TO ADD A NEW UPDATER?</u><BR>
48
 * Adding a new updater currently still needs adjustment at multiple places.
49
 * <BR>
50
 * <BR>1.) Increment {@link CdmMetaData} schema version number and term version number.
51
 * <BR>2.) Create a new class instance of {@link SchemaUpdaterBase} (e.g. by copying an old one).
52
 * <BR>3.) Update startSchemaVersion and endSchemaVersion in this new class, where startSchemaVersion
53
 * is the old schema version and endSchemaVersion is the new schema version.
54
 * <BR>4.) Implement {@link ISchemaUpdater#getPreviousUpdater()} and {@link ISchemaUpdater#getNextUpdater()}
55
 * in a way that the former returns an instance of the previous schema updater and the later returns null (for now).
56
 * <BR>5.) Go to the previous schema updater class and adjust {@link ISchemaUpdater#getNextUpdater()}
57
 * in a way that it returns an instance of the newly created updater.
58
 * <BR>6.) Repeat steps 2.-5. for {@link ITermUpdater}
59
 * <BR>7.) Adjust {@link #getCurrentSchemaUpdater()} and {@link #getCurrentTermUpdater()} to return
60
 * instances of the newly created updaters.
61
 *
62
 * @see ISchemaUpdater
63
 * @see ITermUpdater
64
 * @see ISchemaUpdaterStep
65
 * @see ITermUpdaterStep
66
 *
67
 * @author a.mueller
68
 * @date 10.09.2010
69
 *
70
 */
71
public class CdmUpdater {
72
    private static final Logger logger = Logger.getLogger(CdmUpdater.class);
73

  
74
    public static CdmUpdater NewInstance(){
75
        return new CdmUpdater();
76
    }
77

  
78

  
79
    private ITermUpdater getCurrentTermUpdater() {
80
        return TermUpdater_35_36.NewInstance();
81
    }
82

  
83
    /**
84
     * Returns the current CDM updater
85
     * @return
86
     */
87
    private ISchemaUpdater getCurrentSchemaUpdater() {
88
        return SchemaUpdater_35_36.NewInstance();
89
    }
90

  
91
    /**
92
     * @param datasource
93
     * @param monitor may be <code>null</code>
94
     * @return
95
     */
96
    public boolean updateToCurrentVersion(ICdmDataSource datasource, IProgressMonitor monitor){
97
        boolean result = true;
98
        if (monitor == null){
99
            monitor = DefaultProgressMonitor.NewInstance();
100
        }
101
        CaseType caseType = CaseType.caseTypeOfDatasource(datasource);
102

  
103
        ISchemaUpdater currentSchemaUpdater = getCurrentSchemaUpdater();
104
        // TODO do we really always update the terms??
105
        ITermUpdater currentTermUpdater = getCurrentTermUpdater();
106

  
107
        int steps = currentSchemaUpdater.countSteps(datasource, monitor, caseType);
108
        steps += currentTermUpdater.countSteps(datasource, monitor, caseType);
109
        steps++;  //for hibernate_sequences update
110

  
111
        String taskName = "Update to schema version " + currentSchemaUpdater.getTargetVersion() + " and to term version " + currentTermUpdater.getTargetVersion(); //+ currentSchemaUpdater.getVersion();
112
        monitor.beginTask(taskName, steps);
113

  
114
        try {
115
            datasource.startTransaction();
116
            result &= currentSchemaUpdater.invoke(datasource, monitor, caseType);
117
            if (result == true){
118
                result &= currentTermUpdater.invoke(datasource, monitor, caseType);
119
                updateHibernateSequence(datasource, monitor, caseType);
120
            }
121
            if (result == false){
122
                datasource.rollback();
123
            }else{
124
                datasource.commitTransaction();
125
            }
126

  
127
        } catch (Exception e) {
128
            result = false;
129
            monitor.warning("Stopped schema updater");
130
        } finally {
131
            String message = "Update finished " + (result ? "successfully" : "with ERRORS");
132
            monitor.subTask(message);
133
            if (!result){
134
                monitor.warning(message);
135
                monitor.setCanceled(true);
136
            }else{
137
                monitor.done();
138
            }
139
            logger.info(message);
140
        }
141

  
142
        return result;
143
    }
144

  
145

  
146

  
147
    /**
148
     * Updating terms often inserts new terms, vocabularies and representations.
149
     * Therefore the counter in hibernate_sequences must be increased.
150
     * We do this once at the end of term updating.
151
     * @param caseType
152
     * @return true if update was successful, false otherwise
153
     */
154
    private boolean updateHibernateSequence(ICdmDataSource datasource, IProgressMonitor monitor, CaseType caseType) {
155
        boolean result = true;
156
        monitor.subTask("Update hibernate sequences");
157
        try {
158
            String sql = "SELECT * FROM hibernate_sequences ";
159
            ResultSet rs = datasource.executeQuery(sql);
160
            while (rs.next()){
161
                String table = rs.getString("sequence_name");
162
                Integer val = rs.getInt("next_val");
163
                result &= updateSingleValue(datasource, monitor, table, val, caseType);
164
            }
165
        } catch (Exception e) {
166
            String message = "Exception occurred when trying to update hibernate_sequences table: " + e.getMessage();
167
            monitor.warning(message, e);
168
            logger.error(message);
169
            result = false;
170
        }finally{
171
            monitor.worked(1);
172
        }
173
        return result;
174
    }
175

  
176
    /**
177
     *
178
     * @param datasource
179
     * @param monitor
180
     * @param table
181
     * @param oldVal
182
     * @param caseType
183
     * @return
184
     */
185
    private boolean updateSingleValue(ICdmDataSource datasource, IProgressMonitor monitor, String table, Integer oldVal, CaseType caseType){
186
        if (table.equals("default")){  //found in flora central africa test database
187
            return true;
188
        }
189
        try {
190
            Integer newVal;
191
            try {
192
                String id = table.equalsIgnoreCase("AuditEvent")? "revisionNumber" : "id";
193
                String sql = " SELECT max(%s) FROM %s ";
194
                newVal = (Integer)datasource.getSingleValue(String.format(sql, id, caseType.transformTo(table)));
195
            } catch (Exception e) {
196
                String message = "Could not retrieve max value for table '%s'. Will not update hibernate_sequence for this table. " +
197
                        "Usually this will not cause problems, however, if new data has been added to " +
198
                        "this table by the update script one may encounter 'unique identifier' " +
199
                        "exceptions when trying to add further data.";
200
                monitor.warning(String.format(message,table), e);
201
                //TODO
202
                return true;
203
            }
204

  
205
            if (newVal != null){
206
                //This is how {@link PooledOptimizer#generate(org.hibernate.id.enhanced.AccessCallback)} works
207
                //it substracts the increment size from the value in hibernate_sequences to get the initial value.
208
                //Haven't checked why.
209
                //For the correct increment size see eu.etaxonomy.cdm.model.common.package-info.java
210
                int incrementSize = 10;
211
                newVal = newVal + incrementSize;
212
                if (newVal != null && newVal >= oldVal){
213
                    String sql = " UPDATE hibernate_sequences " +
214
                            " SET next_val = %d " +
215
                            " WHERE sequence_name = '%s' ";
216
                    datasource.executeUpdate(String.format(sql, newVal + 1 , table) );
217
                }
218
            }
219
            return true;
220
        } catch (Exception e) {
221
            String message = "Exception occurred when trying to read or update hibernate_sequences table for value " + table + ": " + e.getMessage();
222
            monitor.warning(message, e);
223
            logger.error(message);
224
            return false;
225
        }
226

  
227
    }
228

  
229

  
230

  
231
    /**
232
     *
233
     *
234
     * @param args SERVER DB_NAME1[,DB_NAME2,...] [USER] [PASSWORD] [PORT]
235
     */
236
    public static void main(String[] args) {
237
//        logger.warn("main method not yet fully implemented (only works with mysql!!!)");
238
//        if(args.length < 2){
239
//            logger.error("Arguments missing: server database [username [password]]");
240
//        }
241
        //TODO better implementation
242
        CdmUpdater myUpdater = new CdmUpdater();
243
        System.out.println("CdmUpdater\nArguments: SERVER DB_NAME1[,DB_NAME2,...] [USER] [PASSWORD] [PORT]");
244
        String server = args[0];
245
        String database  = args[1];
246
        String[] databaseNames = StringUtils.split(database, ',');
247
        String username = args.length > 2 ? args[2] : null;
248
        String password  = args.length > 3 ? args[3] : null;
249
        int port  = 3306;
250
        if( args.length > 4){
251
            try {
252
                port = Integer.parseInt(args[4]);
253
            } catch (Exception e) {
254
                // ignore
255
            }
256
        }
257
        System.out.println("Number of databases to update: " + databaseNames.length);
258
        for(String dnName : databaseNames){
259
            System.out.println(dnName + " UPDATE ...");
260
            ICdmDataSource dataSource = CdmDataSource.NewMySqlInstance(server, dnName, port, username, password, null);
261
            boolean success = myUpdater.updateToCurrentVersion(dataSource, null);
262
            System.out.println(dnName + " DONE " + (success ? "successfully" : "with ERRORS"));
263
            System.out.println("====================================================================");
264

  
265
        }
266
    }
267

  
268
}

Also available in: Unified diff