Project

General

Profile

Download (11.1 KB) Statistics
| Branch: | Tag: | Revision:
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.v36_40.SchemaUpdater_36_40;
22
import eu.etaxonomy.cdm.database.update.v36_40.TermUpdater_36_40;
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_36_40.NewInstance();
81
    }
82

    
83
    /**
84
     * Returns the current CDM updater
85
     * @return
86
     */
87
    private ISchemaUpdater getCurrentSchemaUpdater() {
88
        return SchemaUpdater_36_40.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
}
(3-3/34)