OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 20 | Rev 26 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
18 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.erp.modules;
15
 
16
import org.openconcerto.erp.config.MainFrame;
17
import org.openconcerto.sql.Configuration;
19 ilm 18
import org.openconcerto.sql.element.SQLElement;
19
import org.openconcerto.sql.element.SQLElementDirectory;
25 ilm 20
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
18 ilm 21
import org.openconcerto.sql.model.DBFileCache;
22
import org.openconcerto.sql.model.DBItemFileCache;
23
import org.openconcerto.sql.model.DBRoot;
25 ilm 24
import org.openconcerto.sql.model.SQLDataSource;
19 ilm 25
import org.openconcerto.sql.model.SQLField;
26
import org.openconcerto.sql.model.SQLName;
18 ilm 27
import org.openconcerto.sql.model.SQLRow;
28
import org.openconcerto.sql.model.SQLRowListRSH;
29
import org.openconcerto.sql.model.SQLRowValues;
30
import org.openconcerto.sql.model.SQLSelect;
31
import org.openconcerto.sql.model.SQLSyntax;
25 ilm 32
import org.openconcerto.sql.model.SQLSystem;
18 ilm 33
import org.openconcerto.sql.model.SQLTable;
34
import org.openconcerto.sql.model.Where;
35
import org.openconcerto.sql.model.graph.DirectedEdge;
19 ilm 36
import org.openconcerto.sql.utils.AlterTable;
37
import org.openconcerto.sql.utils.ChangeTable;
38
import org.openconcerto.sql.utils.DropTable;
18 ilm 39
import org.openconcerto.sql.utils.SQLCreateTable;
40
import org.openconcerto.sql.utils.SQLUtils;
41
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
19 ilm 42
import org.openconcerto.sql.view.list.RowAction;
43
import org.openconcerto.utils.CollectionUtils;
18 ilm 44
import org.openconcerto.utils.ExceptionHandler;
25 ilm 45
import org.openconcerto.utils.FileUtils;
19 ilm 46
import org.openconcerto.utils.StringUtils;
18 ilm 47
import org.openconcerto.utils.Tuple2;
48
import org.openconcerto.utils.cc.IClosure;
19 ilm 49
import org.openconcerto.utils.cc.IdentityHashSet;
25 ilm 50
import org.openconcerto.utils.cc.IdentitySet;
18 ilm 51
 
52
import java.io.File;
53
import java.io.FileFilter;
54
import java.io.IOException;
19 ilm 55
import java.io.InputStream;
18 ilm 56
import java.sql.SQLException;
57
import java.util.ArrayList;
58
import java.util.Arrays;
59
import java.util.Collection;
60
import java.util.Collections;
61
import java.util.HashMap;
62
import java.util.HashSet;
63
import java.util.LinkedHashMap;
19 ilm 64
import java.util.LinkedHashSet;
18 ilm 65
import java.util.List;
66
import java.util.Map;
25 ilm 67
import java.util.Map.Entry;
20 ilm 68
import java.util.Set;
19 ilm 69
import java.util.logging.Logger;
18 ilm 70
import java.util.prefs.Preferences;
71
 
19 ilm 72
import javax.swing.JMenuItem;
18 ilm 73
import javax.swing.SwingUtilities;
74
 
75
import org.jgrapht.DirectedGraph;
76
import org.jgrapht.EdgeFactory;
77
import org.jgrapht.graph.SimpleDirectedGraph;
78
 
79
/**
80
 * Hold the list of known modules and their status.
81
 *
82
 * @author Sylvain CUAZ
83
 */
84
public class ModuleManager {
85
 
19 ilm 86
    private static final Logger L = Logger.getLogger(ModuleManager.class.getPackage().getName());
87
 
18 ilm 88
    private static final int MIN_VERSION = 0;
89
    private static final String MODULE_COLNAME = "MODULE_NAME";
90
    private static final String MODULE_VERSION_COLNAME = "MODULE_VERSION";
19 ilm 91
    private static final String TABLE_COLNAME = "TABLE";
92
    private static final String FIELD_COLNAME = "FIELD";
93
    private static final String ISKEY_COLNAME = "KEY";
18 ilm 94
    private static final String FWK_MODULE_TABLENAME = "FWK_MODULE_METADATA";
95
    private static ModuleManager instance = null;
96
 
97
    public static synchronized ModuleManager getInstance() {
98
        if (instance == null)
99
            instance = new ModuleManager();
100
        return instance;
101
    }
102
 
103
    // only one version of each module
104
    private final Map<String, ModuleFactory> factories;
105
    private final Map<String, AbstractModule> runningModules;
19 ilm 106
    private final Map<String, Collection<SQLElement>> modulesElements;
107
    private final Map<String, ComponentsContext> modulesComponents;
18 ilm 108
    private final DirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>> dependencyGraph;
109
 
25 ilm 110
    private DBRoot root;
111
    private Configuration conf;
112
 
18 ilm 113
    public ModuleManager() {
114
        this.factories = new HashMap<String, ModuleFactory>();
115
        this.runningModules = new HashMap<String, AbstractModule>();
116
        this.dependencyGraph = new SimpleDirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>>(new EdgeFactory<ModuleFactory, DirectedEdge<ModuleFactory>>() {
117
            @Override
118
            public DirectedEdge<ModuleFactory> createEdge(ModuleFactory sourceVertex, ModuleFactory targetVertex) {
119
                return new DirectedEdge<ModuleFactory>(sourceVertex, targetVertex);
120
            }
121
        });
19 ilm 122
        this.modulesElements = new HashMap<String, Collection<SQLElement>>();
123
        this.modulesComponents = new HashMap<String, ComponentsContext>();
25 ilm 124
 
125
        this.root = null;
126
        this.conf = null;
18 ilm 127
    }
128
 
129
    // *** factories (thread-safe)
130
 
131
    public final int addFactories(final File dir) {
19 ilm 132
        if (!dir.exists()) {
25 ilm 133
            L.warning("Module factory directory not found: " + dir.getAbsolutePath());
19 ilm 134
            return 0;
135
        }
18 ilm 136
        final File[] jars = dir.listFiles(new FileFilter() {
137
            @Override
138
            public boolean accept(File f) {
139
                return f.getName().endsWith(".jar");
140
            }
141
        });
142
        int i = 0;
19 ilm 143
        if (jars != null) {
144
            for (final File jar : jars) {
145
                try {
146
                    this.addFactory(new JarModuleFactory(jar));
147
                    i++;
148
                } catch (Exception e) {
25 ilm 149
                    L.warning("Couldn't add " + jar);
19 ilm 150
                    e.printStackTrace();
151
                }
18 ilm 152
            }
153
        }
154
        return i;
155
    }
156
 
157
    public final ModuleFactory addFactoryFromPackage(File jar) throws IOException {
158
        final ModuleFactory f = new JarModuleFactory(jar);
159
        this.addFactory(f);
160
        return f;
161
    }
162
 
163
    /**
164
     * Adds a factory.
165
     *
166
     * @param f the factory to add.
167
     * @return the ID of the factory.
168
     */
169
    public final String addFactory(ModuleFactory f) {
170
        return this.addFactory(f, false, false);
171
    }
172
 
173
    public final String addFactoryAndStart(final ModuleFactory f, final boolean persistent) {
174
        return this.addFactory(f, true, persistent);
175
    }
176
 
177
    private final String addFactory(final ModuleFactory f, final boolean start, final boolean persistent) {
178
        synchronized (this.factories) {
25 ilm 179
            final ModuleFactory prev = this.factories.put(f.getID(), f);
180
            if (prev != null)
181
                L.info("Changing the factory for " + f.getID() + "\nfrom\t" + prev + "\nto\t" + f);
18 ilm 182
        }
183
        if (start)
184
            this.invoke(new IClosure<ModuleManager>() {
185
                @Override
186
                public void executeChecked(ModuleManager input) {
187
                    try {
188
                        startModule(f.getID(), persistent);
189
                    } catch (Exception e) {
190
                        ExceptionHandler.handle(MainFrame.getInstance(), "Unable to start " + f, e);
191
                    }
192
                }
193
            });
194
        return f.getID();
195
    }
196
 
197
    public final Map<String, ModuleFactory> getFactories() {
25 ilm 198
        synchronized (this.factories) {
199
            return new HashMap<String, ModuleFactory>(this.factories);
200
        }
18 ilm 201
    }
202
 
19 ilm 203
    private ModuleFactory getFactory(final String id) {
25 ilm 204
        final ModuleFactory res;
205
        synchronized (this.factories) {
206
            res = this.factories.get(id);
207
        }
19 ilm 208
        if (res == null)
209
            throw new IllegalArgumentException("No factory for " + id);
210
        return res;
211
    }
212
 
18 ilm 213
    public final void removeFactory(String id) {
214
        synchronized (this.factories) {
215
            this.factories.remove(id);
216
        }
217
    }
218
 
219
    /**
220
     * Test if the passed factory can create modules.
221
     *
222
     * @param factory the factory to test.
223
     * @return <code>true</code> if the factory can create modules.
224
     */
225
    public final boolean canFactoryCreate(final ModuleFactory factory) {
226
        return canFactoryCreate(factory, new LinkedHashMap<ModuleFactory, Boolean>());
227
    }
228
 
229
    private final boolean canFactoryCreate(final ModuleFactory factory, final LinkedHashMap<ModuleFactory, Boolean> beenThere) {
230
        // TRUE : the factory has already been tested
231
        if (beenThere.get(factory) == Boolean.TRUE)
232
            return true;
233
        if (beenThere.get(factory) == Boolean.FALSE)
234
            throw new IllegalStateException("Cycle detected : " + beenThere);
235
        // null : we've never encountered this factory
236
        beenThere.put(factory, Boolean.FALSE);
237
        synchronized (this.factories) {
238
            for (final String requiredID : factory.getRequiredIDs()) {
239
                final ModuleFactory f = this.factories.get(requiredID);
240
                if (f == null || !factory.isRequiredFactoryOK(f) || !canFactoryCreate(f, beenThere))
241
                    return false;
242
            }
243
        }
244
        // put factory at the end of the map
245
        beenThere.remove(factory);
246
        beenThere.put(factory, Boolean.TRUE);
247
        return true;
248
    }
249
 
250
    // *** modules (in EDT)
251
 
252
    /**
25 ilm 253
     * Call the passed closure at a time when modules can be started. In particular this manager has
254
     * been set up and the {@link MainFrame#getInstance() main frame} has been created.
255
     *
256
     * @param c the closure to execute.
18 ilm 257
     */
258
    public void invoke(final IClosure<ModuleManager> c) {
259
        MainFrame.invoke(new Runnable() {
260
            @Override
261
            public void run() {
262
                c.executeChecked(ModuleManager.this);
263
            }
264
        });
265
    }
266
 
19 ilm 267
    // call registerSQLElements() for any installed modules that might need it
268
    // (e.g. if a module created a child table, as long as the table is in the database it needs to
269
    // be archived along its parent)
270
    private void registerRequiredModules() throws Exception {
271
        final List<String> modulesToStart = new ArrayList<String>();
272
        try {
25 ilm 273
            final Map<String, ModuleFactory> factories = this.getFactories();
19 ilm 274
            for (final Entry<String, ModuleVersion> e : this.getDBInstalledModules().entrySet()) {
275
                final String moduleID = e.getKey();
276
                // modules that just add non-key fields are not required
277
                if (this.areElementsNeeded(moduleID)) {
25 ilm 278
                    final ModuleFactory moduleFactory = factories.get(moduleID);
19 ilm 279
                    final String error;
280
                    if (moduleFactory == null)
281
                        error = "Module '" + moduleID + "' non disponible.";
282
                    else if (!moduleFactory.getVersion().equals(e.getValue()))
283
                        error = "Mauvaise version pour '" + moduleID + "'. La version " + moduleFactory.getVersion() + " est disponible mais " + e.getValue() + " est requise.";
284
                    // check canCreate() after since it's more efficient
285
                    else
286
                        error = null;
287
                    if (error != null) {
288
                        // TODO open GUI to resolve the issue
25 ilm 289
                        throw new Exception(error);
19 ilm 290
                    } else {
291
                        modulesToStart.add(moduleID);
292
                    }
293
                }
294
            }
295
        } catch (Exception e) {
25 ilm 296
            throw new Exception("Impossible de déterminer les modules requis", e);
19 ilm 297
        }
25 ilm 298
        final Tuple2<Map<String, AbstractModule>, Set<String>> modules = this.createModules(modulesToStart, false, true);
19 ilm 299
        if (modules.get1().size() > 0)
25 ilm 300
            throw new Exception("Impossible de créer les modules " + modules.get1());
19 ilm 301
        for (final AbstractModule m : modules.get0().values())
302
            this.registerSQLElements(m);
303
    }
304
 
305
    /**
25 ilm 306
     * Initialise the module manager.
19 ilm 307
     *
25 ilm 308
     * @param root the root where the modules install.
309
     * @param conf the configuration the modules change.
19 ilm 310
     * @throws Exception if required modules couldn't be registered.
311
     */
25 ilm 312
    public synchronized final void setup(final DBRoot root, final Configuration conf) throws Exception {
313
        if (root == null || conf == null)
314
            throw new NullPointerException();
315
        if (this.root != null || getConf() != null)
316
            throw new IllegalStateException("Already setup");
317
        // modulesElements can be non empty, if a previous setup() failed
318
        assert this.runningModules.isEmpty() && this.modulesComponents.isEmpty() : "Modules cannot start without root & conf";
319
        this.root = root;
320
        this.conf = conf;
19 ilm 321
        try {
25 ilm 322
            this.registerRequiredModules();
19 ilm 323
        } catch (Exception e) {
25 ilm 324
            // allow setup() to be called again
325
            this.root = null;
326
            this.conf = null;
327
            throw e;
19 ilm 328
        }
25 ilm 329
        assert this.runningModules.isEmpty() && this.modulesComponents.isEmpty() : "registerRequiredModules() should not start modules";
19 ilm 330
    }
331
 
18 ilm 332
    private Preferences getPrefs() {
333
        // modules are installed per business entity (perhaps we could add a per user option, i.e.
334
        // for all businesses of all databases)
335
        final StringBuilder path = new StringBuilder(32);
25 ilm 336
        for (final String item : DBFileCache.getJDBCAncestorNames(getRoot(), true)) {
19 ilm 337
            path.append(StringUtils.getBoundedLengthString(DBItemFileCache.encode(item), Preferences.MAX_NAME_LENGTH));
18 ilm 338
            path.append('/');
339
        }
340
        // path must not end with '/'
341
        path.setLength(path.length() - 1);
342
        return Preferences.userNodeForPackage(ModuleManager.class).node(path.toString());
343
    }
344
 
345
    private Preferences getRunningIDsPrefs() {
346
        return getPrefs().node("toRun");
347
    }
348
 
349
    protected final boolean isModuleInstalledLocally(String id) {
25 ilm 350
        return getLocalVersionFile(id).exists();
18 ilm 351
    }
352
 
19 ilm 353
    protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
25 ilm 354
        final File versionFile = getLocalVersionFile(id);
355
        if (versionFile.exists()) {
356
            try {
357
                return new ModuleVersion(Long.valueOf(FileUtils.read(versionFile)));
358
            } catch (IOException e) {
359
                throw new IllegalStateException("Couldn't get installed version of " + id, e);
360
            }
361
        } else {
362
            return null;
363
        }
19 ilm 364
    }
365
 
18 ilm 366
    public final Collection<String> getModulesInstalledLocally() {
25 ilm 367
        return getModulesVersionInstalledLocally().keySet();
18 ilm 368
    }
369
 
19 ilm 370
    public final Map<String, ModuleVersion> getModulesVersionInstalledLocally() {
371
        final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
25 ilm 372
        final File dir = getLocalDirectory();
373
        for (final File d : dir.listFiles()) {
374
            final String id = d.getName();
375
            final ModuleVersion version = getModuleVersionInstalledLocally(id);
376
            if (version != null)
377
                res.put(id, version);
19 ilm 378
        }
379
        return res;
380
    }
381
 
18 ilm 382
    private void setModuleInstalledLocally(ModuleFactory f, boolean b) {
25 ilm 383
        try {
384
            if (b) {
385
                final ModuleVersion vers = f.getVersion();
386
                if (vers.getMerged() < MIN_VERSION)
387
                    throw new IllegalStateException("Invalid version : " + vers);
388
                final File versionFile = getLocalVersionFile(f.getID());
389
                FileUtils.mkdir_p(versionFile.getParentFile());
390
                FileUtils.write(String.valueOf(vers.getMerged()), versionFile);
391
            } else {
392
                // perhaps add a parameter to only remove the versionFile
393
                FileUtils.rm_R(getLocalDirectory(f.getID()));
394
            }
395
        } catch (IOException e) {
396
            throw new IllegalStateException("Couldn't change installed status of " + f, e);
18 ilm 397
        }
398
    }
399
 
400
    private SQLTable getInstalledTable(final DBRoot r) throws SQLException {
401
        if (!r.contains(FWK_MODULE_TABLENAME)) {
19 ilm 402
            // store :
403
            // - currently installed module (TABLE_COLNAME & FIELD_COLNAME are null)
404
            // - created tables (FIELD_COLNAME is null)
405
            // - created fields (and whether they are keys)
18 ilm 406
            final SQLCreateTable createTable = new SQLCreateTable(r, FWK_MODULE_TABLENAME);
407
            createTable.setPlain(true);
408
            createTable.addColumn(SQLSyntax.ID_NAME, createTable.getSyntax().getPrimaryIDDefinition());
409
            createTable.addVarCharColumn(MODULE_COLNAME, 128);
19 ilm 410
            createTable.addColumn(TABLE_COLNAME, "varchar(128) NULL");
411
            createTable.addColumn(FIELD_COLNAME, "varchar(128) NULL");
412
            createTable.addColumn(ISKEY_COLNAME, "boolean NULL");
18 ilm 413
            createTable.addColumn(MODULE_VERSION_COLNAME, "bigint NOT NULL");
414
 
19 ilm 415
            createTable.addUniqueConstraint("uniqModule", Arrays.asList(MODULE_COLNAME, TABLE_COLNAME, FIELD_COLNAME));
18 ilm 416
 
25 ilm 417
            SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
18 ilm 418
                @Override
419
                public Object create() throws SQLException {
420
                    r.getDBSystemRoot().getDataSource().execute(createTable.asString());
421
                    r.getSchema().updateVersion();
422
                    SQLTable.setUndefID(r.getSchema(), createTable.getName(), null);
423
                    return null;
424
                }
425
            });
426
            r.refetch();
427
        }
428
        return r.getTable(FWK_MODULE_TABLENAME);
429
    }
430
 
25 ilm 431
    protected final DBRoot getRoot() {
432
        return this.root;
18 ilm 433
    }
434
 
25 ilm 435
    private SQLDataSource getDS() {
436
        return getRoot().getDBSystemRoot().getDataSource();
437
    }
438
 
439
    protected final Configuration getConf() {
440
        return this.conf;
441
    }
442
 
443
    private SQLElementDirectory getDirectory() {
444
        return getConf().getDirectory();
445
    }
446
 
447
    private final File getLocalDirectory() {
448
        return new File(this.getConf().getConfDir(getRoot()), "modules");
449
    }
450
 
451
    protected final File getLocalDirectory(final String id) {
452
        return new File(this.getLocalDirectory(), id);
453
    }
454
 
455
    private final File getLocalVersionFile(final String id) {
456
        return new File(this.getLocalDirectory(id), "version");
457
    }
458
 
18 ilm 459
    public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
19 ilm 460
        return getDBInstalledModules(id).get(id);
18 ilm 461
    }
462
 
463
    public final Map<String, ModuleVersion> getDBInstalledModules() throws SQLException {
19 ilm 464
        return getDBInstalledModules(null);
465
    }
466
 
467
    private final Map<String, ModuleVersion> getDBInstalledModules(final String id) throws SQLException {
18 ilm 468
        final SQLTable installedTable = getInstalledTable(getRoot());
469
        final SQLSelect sel = new SQLSelect(installedTable.getBase()).addSelectStar(installedTable);
19 ilm 470
        sel.setWhere(Where.isNull(installedTable.getField(TABLE_COLNAME)).and(Where.isNull(installedTable.getField(FIELD_COLNAME))));
471
        if (id != null)
472
            sel.andWhere(new Where(installedTable.getField(MODULE_COLNAME), "=", id));
18 ilm 473
        final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
474
        for (final SQLRow r : SQLRowListRSH.execute(sel)) {
475
            res.put(r.getString(MODULE_COLNAME), new ModuleVersion(r.getLong(MODULE_VERSION_COLNAME)));
476
        }
477
        return res;
478
    }
479
 
480
    private void setDBInstalledModule(ModuleFactory f, boolean b) throws SQLException {
481
        final SQLTable installedTable = getInstalledTable(getRoot());
482
        final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", f.getID());
19 ilm 483
        final Where noItemsW = Where.isNull(installedTable.getField(TABLE_COLNAME)).and(Where.isNull(installedTable.getField(FIELD_COLNAME)));
484
        final Where w = idW.and(noItemsW);
18 ilm 485
        if (b) {
486
            final SQLSelect sel = new SQLSelect(installedTable.getBase());
487
            sel.addSelect(installedTable.getKey());
19 ilm 488
            sel.setWhere(w);
18 ilm 489
            final Number id = (Number) installedTable.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
490
            final SQLRowValues vals = new SQLRowValues(installedTable);
491
            vals.put(MODULE_VERSION_COLNAME, f.getVersion().getMerged());
492
            if (id != null) {
493
                vals.setID(id);
494
                vals.update();
495
            } else {
496
                vals.put(MODULE_COLNAME, f.getID());
19 ilm 497
                vals.put(TABLE_COLNAME, null);
498
                vals.put(FIELD_COLNAME, null);
18 ilm 499
                vals.insert();
500
            }
501
        } else {
19 ilm 502
            installedTable.getDBSystemRoot().getDataSource().execute("DELETE FROM " + installedTable.getSQLName().quote() + " WHERE " + w.getClause());
18 ilm 503
        }
504
    }
505
 
19 ilm 506
    protected final boolean isModuleInstalledLocallyOrInDB(String id) throws SQLException {
507
        return this.isModuleInstalledLocally(id) || getDBInstalledModuleVersion(id) != null;
508
    }
509
 
510
    public final Tuple2<Set<String>, Set<SQLName>> getCreatedItems(final String id) throws SQLException {
511
        final SQLTable installedTable = getInstalledTable(getRoot());
512
        final SQLSelect sel = new SQLSelect(installedTable.getBase());
513
        sel.addSelect(installedTable.getKey());
514
        sel.addSelect(installedTable.getField(TABLE_COLNAME));
515
        sel.addSelect(installedTable.getField(FIELD_COLNAME));
516
        sel.setWhere(new Where(installedTable.getField(MODULE_COLNAME), "=", id).and(Where.isNotNull(installedTable.getField(TABLE_COLNAME))));
517
        final Set<String> tables = new HashSet<String>();
518
        final Set<SQLName> fields = new HashSet<SQLName>();
519
        for (final SQLRow r : SQLRowListRSH.execute(sel)) {
520
            final String tableName = r.getString(TABLE_COLNAME);
521
            final String fieldName = r.getString(FIELD_COLNAME);
522
            if (fieldName == null)
523
                tables.add(tableName);
524
            else
525
                fields.add(new SQLName(tableName, fieldName));
526
        }
527
        return Tuple2.create(tables, fields);
528
    }
529
 
530
    private void updateModuleFields(ModuleFactory factory, final DBContext ctxt) throws SQLException {
531
        final SQLTable installedTable = getInstalledTable(getRoot());
532
        final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", factory.getID());
533
        // removed items
534
        {
535
            final List<Where> dropWheres = new ArrayList<Where>();
536
            for (final String dropped : ctxt.getRemovedTables()) {
537
                dropWheres.add(new Where(installedTable.getField(TABLE_COLNAME), "=", dropped));
538
            }
539
            for (final SQLName dropped : ctxt.getRemovedFieldsFromExistingTables()) {
540
                dropWheres.add(new Where(installedTable.getField(TABLE_COLNAME), "=", dropped.getItem(0)).and(new Where(installedTable.getField(FIELD_COLNAME), "=", dropped.getItem(1))));
541
            }
542
            if (dropWheres.size() > 0)
543
                installedTable.getDBSystemRoot().getDataSource().execute("DELETE FROM " + installedTable.getSQLName().quote() + " WHERE " + Where.or(dropWheres).and(idW).getClause());
544
        }
545
        // added items
546
        {
547
            final SQLRowValues vals = new SQLRowValues(installedTable);
548
            vals.put(MODULE_VERSION_COLNAME, factory.getVersion().getMerged());
549
            vals.put(MODULE_COLNAME, factory.getID());
550
            for (final String added : ctxt.getAddedTables()) {
551
                vals.put(TABLE_COLNAME, added).put(FIELD_COLNAME, null).insert();
552
                final SQLTable t = ctxt.getRoot().findTable(added);
553
                for (final SQLField field : t.getFields()) {
554
                    vals.put(TABLE_COLNAME, added).put(FIELD_COLNAME, field.getName()).put(ISKEY_COLNAME, field.isKey()).insert();
555
                }
556
                vals.remove(ISKEY_COLNAME);
557
            }
558
            for (final SQLName added : ctxt.getAddedFieldsToExistingTables()) {
559
                final SQLTable t = ctxt.getRoot().findTable(added.getItem(0));
560
                final SQLField field = t.getField(added.getItem(1));
561
                vals.put(TABLE_COLNAME, t.getName()).put(FIELD_COLNAME, field.getName()).put(ISKEY_COLNAME, field.isKey()).insert();
562
            }
563
            vals.remove(ISKEY_COLNAME);
564
        }
565
        // MAYBE pass alreadyCreatedItems to avoid 1 request
566
        final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(factory.getID());
567
        final boolean atLeast1ItemIsCreated = createdItems.get0().size() + createdItems.get1().size() > 0;
568
        setDBInstalledModule(factory, atLeast1ItemIsCreated);
569
    }
570
 
571
    private void removeModuleFields(ModuleFactory f) throws SQLException {
572
        final SQLTable installedTable = getInstalledTable(getRoot());
573
        final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", f.getID());
574
        installedTable.getDBSystemRoot().getDataSource()
575
                .execute("DELETE FROM " + installedTable.getSQLName().quote() + " WHERE " + Where.isNotNull(installedTable.getField(TABLE_COLNAME)).and(idW).getClause());
576
        setDBInstalledModule(f, false);
577
    }
578
 
579
    // true if the module has created a table or a key
580
    private final boolean areElementsNeeded(final String id) throws SQLException {
581
        final SQLTable installedTable = getInstalledTable(getRoot());
582
        final SQLSelect sel = new SQLSelect(installedTable.getBase());
583
        sel.addRawSelect("COUNT(*) > 0", null);
584
        final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", id);
585
        final Where tableCreated = Where.isNotNull(installedTable.getField(TABLE_COLNAME)).and(Where.isNull(installedTable.getField(FIELD_COLNAME)));
586
        final Where keyCreated = Where.isNotNull(installedTable.getField(FIELD_COLNAME)).and(new Where(installedTable.getField(ISKEY_COLNAME), "=", Boolean.TRUE));
587
        sel.setWhere(idW.and(tableCreated.or(keyCreated)));
588
        return (Boolean) installedTable.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
589
    }
590
 
25 ilm 591
    private void install(final AbstractModule module) throws Exception {
18 ilm 592
        final ModuleFactory factory = module.getFactory();
25 ilm 593
        final ModuleVersion localVersion = getModuleVersionInstalledLocally(factory.getID());
594
        final ModuleVersion lastInstalledVersion = getDBInstalledModuleVersion(factory.getID());
595
        final ModuleVersion moduleVersion = module.getFactory().getVersion();
596
        if (lastInstalledVersion != null && moduleVersion.compareTo(lastInstalledVersion) < 0)
597
            throw new IllegalArgumentException("Module older than the one installed in the DB : " + moduleVersion + " < " + lastInstalledVersion);
598
        if (localVersion != null && moduleVersion.compareTo(localVersion) < 0)
599
            throw new IllegalArgumentException("Module older than the one installed locally : " + moduleVersion + " < " + localVersion);
600
        if (!moduleVersion.equals(localVersion) || !moduleVersion.equals(lastInstalledVersion)) {
601
            // local
602
            final File localDir = getLocalDirectory(factory.getID());
603
            // There are 2 choices to handle the update of files :
604
            // 1. copy dir to a new one and pass it to DBContext, then either rename it to dir or
605
            // rename it failed
606
            // 2. copy dir to a backup, pass dir to DBContext, then either remove backup or rename
607
            // it to dir
608
            // Choice 2 is simpler since the module deals with the same directory in both install()
609
            // and start()
610
            final File backupDir;
611
            // check if we need a backup
612
            if (localDir.exists()) {
613
                backupDir = FileUtils.addSuffix(localDir, ".backup");
614
                FileUtils.rm_R(backupDir);
615
                FileUtils.copyDirectory(localDir, backupDir);
616
            } else {
617
                backupDir = null;
618
                FileUtils.mkdir_p(localDir);
619
            }
620
            assert localDir.exists();
621
            try {
622
                SQLUtils.executeAtomic(getDS(), new ConnectionHandlerNoSetup<Object, IOException>() {
623
                    @Override
624
                    public Object handle(SQLDataSource ds) throws SQLException, IOException {
625
                        final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
626
                        final DBContext ctxt = new DBContext(localDir, localVersion, getRoot(), lastInstalledVersion, alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
627
                        // install local
628
                        module.install(ctxt);
629
                        if (!localDir.exists())
630
                            throw new IOException("Modules shouldn't remove their directory");
631
                        // install in DB
632
                        ctxt.execute();
633
                        updateModuleFields(factory, ctxt);
634
                        return null;
635
                    }
636
                });
637
            } catch (Exception e) {
638
                // install did not complete successfully
639
                if (getRoot().getServer().getSQLSystem() == SQLSystem.MYSQL)
640
                    L.warning("MySQL cannot rollback DDL statements");
641
                // keep failed install files and restore previous files
642
                final File failed = FileUtils.addSuffix(localDir, ".failed");
643
                if (failed.exists() && !FileUtils.rmR(failed))
644
                    L.warning("Couldn't remove " + failed);
645
                if (!localDir.renameTo(failed)) {
646
                    L.warning("Couldn't move " + localDir + " to " + failed);
647
                } else {
648
                    assert !localDir.exists();
649
                    // restore if needed
650
                    if (backupDir != null && !backupDir.renameTo(localDir))
651
                        L.warning("Couldn't restore " + backupDir + " to " + localDir);
18 ilm 652
                }
25 ilm 653
                throw e;
654
            }
655
            // DB transaction was committed, remove backup files
656
            assert localDir.exists();
657
            if (backupDir != null)
658
                FileUtils.rm_R(backupDir);
659
            setModuleInstalledLocally(factory, true);
18 ilm 660
        }
25 ilm 661
        assert moduleVersion.equals(getModuleVersionInstalledLocally(factory.getID())) && moduleVersion.equals(getDBInstalledModuleVersion(factory.getID()));
18 ilm 662
    }
663
 
19 ilm 664
    private void registerSQLElements(final AbstractModule module) {
665
        final String id = module.getFactory().getID();
25 ilm 666
        synchronized (this.modulesElements) {
667
            if (!this.modulesElements.containsKey(id)) {
668
                final SQLElementDirectory dir = getDirectory();
669
                final Map<SQLTable, SQLElement> beforeElements = new HashMap<SQLTable, SQLElement>(dir.getElementsMap());
670
                module.setupElements(dir);
671
                final Collection<SQLElement> elements = new ArrayList<SQLElement>();
672
                // use IdentitySet so as not to call equals() since it triggers initFF()
673
                final IdentitySet<SQLElement> beforeElementsSet = new IdentityHashSet<SQLElement>(beforeElements.values());
674
                for (final SQLElement elem : dir.getElements()) {
675
                    if (!beforeElementsSet.contains(elem)) {
676
                        // don't let elements be replaced (it's tricky to restore in unregister())
677
                        if (beforeElements.containsKey(elem.getTable())) {
678
                            L.warning("Trying to replace element for " + elem.getTable() + " with " + elem);
679
                            dir.addSQLElement(beforeElements.get(elem.getTable()));
680
                        } else {
681
                            elements.add(elem);
682
                        }
683
                    }
19 ilm 684
                }
25 ilm 685
                this.modulesElements.put(id, elements);
19 ilm 686
            }
687
        }
688
    }
689
 
690
    private void setupComponents(final AbstractModule module) throws SQLException {
691
        final String id = module.getFactory().getID();
692
        if (!this.modulesComponents.containsKey(id)) {
25 ilm 693
            final SQLElementDirectory dir = getDirectory();
19 ilm 694
            final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(id);
695
            final ComponentsContext ctxt = new ComponentsContext(dir, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
696
            module.setupComponents(ctxt);
697
            this.modulesComponents.put(id, ctxt);
698
        }
699
    }
700
 
25 ilm 701
    public final void startPreviouslyRunningModules() throws Exception {
18 ilm 702
        final List<String> ids = Arrays.asList(getRunningIDsPrefs().keys());
703
        startModules(ids);
704
    }
705
 
706
    public final boolean startModule(final String id) throws Exception {
707
        return this.startModule(id, true);
708
    }
709
 
710
    public final boolean startModule(final String id, final boolean persistent) throws Exception {
711
        final Set<String> res = startModules(Collections.singleton(id), persistent);
712
        return res.isEmpty();
713
    }
714
 
715
    public final Set<String> startModules(final Collection<String> ids, final boolean persistent) throws Exception {
716
        final Set<String> res = startModules(ids);
717
        if (persistent) {
718
            for (final String id : ids) {
719
                assert this.isModuleRunning(id) == !res.contains(id);
720
                if (!res.contains(id))
721
                    getRunningIDsPrefs().put(id, "");
722
            }
723
        }
724
        return res;
725
    }
726
 
727
    private final Set<String> startModules(final Collection<String> ids) throws Exception {
25 ilm 728
        return this.createModules(ids, true, false).get1();
18 ilm 729
    }
730
 
731
    // modules created, and the ones that couldn't
19 ilm 732
    // i.e. if a module was already created it's in neither
25 ilm 733
    private final Tuple2<Map<String, AbstractModule>, Set<String>> createModules(final Collection<String> ids, final boolean start, final boolean inSetup) throws Exception {
734
        // in setup we're not in the EDT, but it's OK since by definition no modules are started
735
        assert SwingUtilities.isEventDispatchThread() || inSetup && this.runningModules.isEmpty();
18 ilm 736
        // add currently running modules so that ModuleFactory can use them
25 ilm 737
        final Map<String, AbstractModule> modules = inSetup ? new LinkedHashMap<String, AbstractModule>(ids.size() * 2) : new LinkedHashMap<String, AbstractModule>(this.runningModules);
18 ilm 738
        final Set<String> cannotCreate = new HashSet<String>();
739
        final LinkedHashMap<ModuleFactory, Boolean> map = new LinkedHashMap<ModuleFactory, Boolean>();
740
        synchronized (this.factories) {
741
            for (final String id : ids) {
19 ilm 742
                final ModuleFactory f = getFactory(id);
18 ilm 743
                if (canFactoryCreate(f, map)) {
744
                    for (final ModuleFactory useableFactory : map.keySet()) {
745
                        if (!modules.containsKey(useableFactory.getID()))
746
                            modules.put(useableFactory.getID(), useableFactory.createModule(Collections.unmodifiableMap(modules)));
747
                    }
748
                } else {
749
                    cannotCreate.add(id);
750
                }
751
            }
752
        }
753
        // only keep modules created by this method
25 ilm 754
        if (!inSetup)
755
            modules.keySet().removeAll(this.runningModules.keySet());
18 ilm 756
 
757
        if (start) {
758
            for (final AbstractModule module : modules.values())
759
                startModule(module);
760
        }
761
 
762
        // remove dependencies
763
        modules.keySet().retainAll(ids);
764
        return Tuple2.create(modules, cannotCreate);
765
    }
766
 
767
    private final boolean startModule(final AbstractModule module) throws Exception {
768
        final ModuleFactory f = module.getFactory();
769
        final String id = f.getID();
770
        if (isModuleRunning(id)) {
771
            return false;
772
        } else {
773
            try {
774
                install(module);
775
            } catch (Exception e) {
776
                throw new Exception("Couldn't install module " + module, e);
777
            }
778
            try {
19 ilm 779
                final InputStream labels = module.getClass().getResourceAsStream("labels.xml");
780
                if (labels != null) {
781
                    try {
25 ilm 782
                        getConf().getTranslator().load(getRoot(), labels);
19 ilm 783
                    } finally {
784
                        labels.close();
785
                    }
786
                }
787
                this.registerSQLElements(module);
788
                this.setupComponents(module);
18 ilm 789
                module.start();
790
            } catch (Exception e) {
791
                throw new Exception("Couldn't start module " + module, e);
792
            }
793
            this.runningModules.put(id, module);
794
 
795
            // update graph
796
            final boolean added = this.dependencyGraph.addVertex(f);
797
            assert added : "Module was already in graph : " + f;
798
            for (final String requiredID : f.getRequiredIDs())
799
                this.dependencyGraph.addEdge(f, this.runningModules.get(requiredID).getFactory());
800
 
801
            return true;
802
        }
803
    }
804
 
805
    public final boolean isModuleRunning(final String id) {
806
        assert SwingUtilities.isEventDispatchThread();
807
        return this.runningModules.containsKey(id);
808
    }
809
 
810
    public final Map<String, AbstractModule> getRunningModules() {
811
        return Collections.unmodifiableMap(this.runningModules);
812
    }
813
 
814
    public final void stopModuleRecursively(final String id) {
815
        if (!this.isModuleRunning(id))
816
            return;
817
 
818
        final ModuleFactory f = this.runningModules.get(id).getFactory();
819
        // the graph has no cycle, so we don't need to protected against infinite loop
820
        for (final DirectedEdge<ModuleFactory> e : new ArrayList<DirectedEdge<ModuleFactory>>(this.dependencyGraph.incomingEdgesOf(f))) {
821
            this.stopModuleRecursively(e.getSource().getID());
822
        }
823
        this.stopModule(id);
824
    }
825
 
826
    public final void stopModule(final String id) {
827
        this.stopModule(id, true);
828
    }
829
 
830
    public final void stopModule(final String id, final boolean persistent) {
831
        assert SwingUtilities.isEventDispatchThread();
832
        if (!this.isModuleRunning(id))
833
            return;
834
 
835
        final ModuleFactory f = this.runningModules.get(id).getFactory();
836
        final Set<DirectedEdge<ModuleFactory>> deps = this.dependencyGraph.incomingEdgesOf(f);
837
        if (deps.size() > 0)
838
            throw new IllegalArgumentException("Dependents still running : " + deps);
839
        this.dependencyGraph.removeVertex(f);
840
        final AbstractModule m = this.runningModules.remove(id);
841
        m.stop();
19 ilm 842
        this.tearDownComponents(m);
18 ilm 843
        if (persistent)
844
            getRunningIDsPrefs().remove(m.getFactory().getID());
845
        assert !this.isModuleRunning(id);
846
    }
847
 
19 ilm 848
    private void unregisterSQLElements(final AbstractModule module) {
849
        final String id = module.getFactory().getID();
25 ilm 850
        synchronized (this.modulesElements) {
851
            if (this.modulesElements.containsKey(id)) {
852
                final Collection<SQLElement> elements = this.modulesElements.remove(id);
853
                final SQLElementDirectory dir = getDirectory();
854
                for (final SQLElement elem : elements)
855
                    dir.removeSQLElement(elem);
856
            }
19 ilm 857
        }
858
    }
859
 
860
    private void tearDownComponents(final AbstractModule module) {
861
        final String id = module.getFactory().getID();
862
        if (this.modulesComponents.containsKey(id)) {
863
            final ComponentsContext ctxt = this.modulesComponents.remove(id);
864
            for (final Entry<SQLElement, Collection<String>> e : ctxt.getFields().entrySet())
865
                for (final String fieldName : e.getValue())
866
                    e.getKey().removeAdditionalField(fieldName);
867
            for (final Entry<SQLElement, Collection<RowAction>> e : ctxt.getRowActions().entrySet())
868
                e.getKey().getRowActions().removeAll(e.getValue());
869
            for (final JMenuItem mi : ctxt.getMenuItems())
870
                MainFrame.getInstance().removeMenuItem(mi);
871
        }
872
    }
873
 
874
    private final List<String> getDBDependentModules(final String id) throws Exception {
875
        final Set<String> tables = getCreatedItems(id).get0();
876
        if (tables.size() == 0)
877
            return Collections.emptyList();
878
 
879
        final SQLTable installedTable = getInstalledTable(getRoot());
880
        final SQLSelect sel = new SQLSelect(installedTable.getBase());
881
        sel.addSelect(installedTable.getField(MODULE_COLNAME));
882
        sel.setWhere(new Where(installedTable.getField(MODULE_COLNAME), "!=", id).and(new Where(installedTable.getField(TABLE_COLNAME), tables)));
883
        @SuppressWarnings("unchecked")
884
        final List<String> res = installedTable.getDBSystemRoot().getDataSource().executeCol(sel.asString());
885
        return res;
886
    }
887
 
888
    // modules needing us are the ones currently started + the ones installed in the database
889
    // that need one of our fields
890
    private final Collection<String> getDependentModules(final String id) throws Exception {
891
        final Set<String> depModules = new HashSet<String>(getDBDependentModules(id));
892
        final AbstractModule runningModule = this.runningModules.get(id);
893
        if (runningModule != null) {
894
            for (final DirectedEdge<ModuleFactory> e : new ArrayList<DirectedEdge<ModuleFactory>>(this.dependencyGraph.incomingEdgesOf(runningModule.getFactory()))) {
895
                depModules.add(e.getSource().getID());
896
            }
897
        }
898
        return depModules;
899
    }
900
 
901
    /**
902
     * The list of modules depending on the passed one.
903
     *
904
     * @param id the module.
905
     * @return the modules needing <code>id</code> (excluding it), in uninstallation order (i.e. the
906
     *         first item isn't depended on).
907
     * @throws Exception if an error occurs.
908
     */
909
    public final List<String> getDependentModulesRecursively(final String id) throws Exception {
910
        final List<String> res = new ArrayList<String>();
911
        for (final String depModule : getDependentModules(id)) {
912
            res.add(depModule);
913
            // the graph has no cycle, so we don't need to protected against infinite loop
914
            res.addAll(this.getDependentModulesRecursively(depModule));
915
        }
916
        Collections.reverse(res);
917
        return res;
918
    }
919
 
920
    // ids + modules depending on them in uninstallation order
921
    private final LinkedHashSet<String> getAllOrderedDependentModulesRecursively(final Set<String> ids) throws Exception {
922
        final LinkedHashSet<String> depModules = new LinkedHashSet<String>();
923
        for (final String id : ids) {
924
            if (!depModules.contains(id)) {
925
                depModules.addAll(getDependentModulesRecursively(id));
926
                // even without this line the result could still contain some of ids if it contained
927
                // a module and one of its dependencies
928
                depModules.add(id);
929
            }
930
        }
931
        return depModules;
932
    }
933
 
934
    /**
935
     * The set of modules depending on the passed ones.
936
     *
937
     * @param ids the modules.
938
     * @return the modules needing <code>ids</code> (excluding them).
939
     * @throws Exception if an error occurs.
940
     */
941
    public final Set<String> getDependentModulesRecursively(final Set<String> ids) throws Exception {
942
        final LinkedHashSet<String> res = getAllOrderedDependentModulesRecursively(ids);
943
        res.removeAll(ids);
944
        return res;
945
    }
946
 
947
    public final Collection<String> uninstall(final Set<String> ids, final boolean recurse) throws Exception {
948
        final Set<String> res;
949
        if (!recurse) {
950
            final LinkedHashSet<String> depModules = getAllOrderedDependentModulesRecursively(ids);
951
            final Collection<String> depModulesNotRequested = CollectionUtils.substract(depModules, ids);
952
            if (!depModulesNotRequested.isEmpty())
953
                throw new IllegalStateException("Dependent modules not uninstalled : " + depModulesNotRequested);
954
            // limit the number of requests
955
            final Map<String, ModuleVersion> dbVersions = this.getDBInstalledModules();
956
            for (final String id : depModules)
957
                this.uninstallUnsafe(id, dbVersions);
958
            res = depModules;
959
        } else {
960
            res = new HashSet<String>();
961
            for (final String id : ids) {
962
                if (!res.contains(id))
963
                    res.addAll(this.uninstall(id, recurse));
964
            }
965
        }
966
        assert (recurse && res.containsAll(ids)) || (!recurse && res.equals(ids));
967
        return res;
968
    }
969
 
18 ilm 970
    public final void uninstall(final String id) throws Exception {
19 ilm 971
        this.uninstall(id, false);
972
    }
18 ilm 973
 
19 ilm 974
    public final Collection<String> uninstall(final String id, final boolean recurse) throws Exception {
975
        // even if it wasn't installed locally we might want to uninstall it from the DB
976
        if (!this.isModuleInstalledLocallyOrInDB(id))
977
            return Collections.emptySet();
978
 
979
        final Set<String> res = new HashSet<String>();
980
        final Collection<String> depModules = getDependentModules(id);
981
        if (depModules.size() > 0) {
982
            if (recurse) {
983
                for (final String depModule : depModules) {
984
                    res.addAll(uninstall(depModule, recurse));
985
                }
986
            } else {
987
                throw new IllegalStateException("Dependent modules not uninstalled : " + depModules);
988
            }
989
        }
990
 
991
        uninstallUnsafe(id, null);
992
        res.add(id);
993
        return res;
994
    }
995
 
996
    // dbVersions parameter to avoid requests to the DB
997
    private void uninstallUnsafe(final String id, Map<String, ModuleVersion> dbVersions) throws SQLException, Exception {
998
        if (dbVersions == null)
999
            dbVersions = this.getDBInstalledModules();
1000
        final ModuleFactory moduleFactory = getFactory(id);
1001
        final ModuleVersion localVersion = this.getModuleVersionInstalledLocally(id);
1002
        final ModuleVersion dbVersion = dbVersions.get(id);
1003
        if (localVersion != null && !moduleFactory.getVersion().equals(localVersion))
1004
            throw new IllegalStateException("Local version not equal : " + localVersion);
1005
        if (dbVersion != null && !moduleFactory.getVersion().equals(dbVersion))
1006
            throw new IllegalStateException("DB version not equal : " + dbVersion);
1007
 
18 ilm 1008
        final AbstractModule module;
1009
        if (!this.isModuleRunning(id)) {
25 ilm 1010
            module = this.createModules(Collections.singleton(id), false, false).get0().get(id);
18 ilm 1011
        } else {
1012
            module = this.runningModules.get(id);
1013
            this.stopModule(id, true);
1014
        }
1015
 
25 ilm 1016
        SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
18 ilm 1017
            @Override
1018
            public Object create() throws SQLException {
25 ilm 1019
                final DBRoot root = getRoot();
1020
                module.uninstall(root);
19 ilm 1021
                unregisterSQLElements(module);
18 ilm 1022
                setModuleInstalledLocally(module.getFactory(), false);
19 ilm 1023
 
1024
                // uninstall from DB
1025
                final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
1026
                final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
1027
                final Set<String> tableNames = createdItems.get0();
1028
                for (final SQLName field : createdItems.get1()) {
1029
                    final SQLField f = root.getDesc(field, SQLField.class);
1030
                    // dropped by DROP TABLE
1031
                    if (!tableNames.contains(f.getTable().getName())) {
20 ilm 1032
                        // cascade needed since the module might have created constraints
1033
                        // (e.g. on H2 a foreign column cannot be dropped)
1034
                        l.add(new AlterTable(f.getTable()).dropColumnCascade(f.getName()));
19 ilm 1035
                    }
1036
                }
1037
                for (final String table : tableNames) {
1038
                    l.add(new DropTable(root.getTable(table)));
1039
                }
1040
                if (l.size() > 0) {
1041
                    for (final String s : ChangeTable.cat(l, root.getName()))
1042
                        root.getDBSystemRoot().getDataSource().execute(s);
1043
                    root.getSchema().updateVersion();
1044
                    root.refetch();
1045
                }
1046
 
1047
                removeModuleFields(module.getFactory());
18 ilm 1048
                return null;
1049
            }
1050
        });
1051
    }
1052
}