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 | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 20 Rev 25
Line 15... Line 15...
15
 
15
 
16
import org.openconcerto.erp.config.MainFrame;
16
import org.openconcerto.erp.config.MainFrame;
17
import org.openconcerto.sql.Configuration;
17
import org.openconcerto.sql.Configuration;
18
import org.openconcerto.sql.element.SQLElement;
18
import org.openconcerto.sql.element.SQLElement;
19
import org.openconcerto.sql.element.SQLElementDirectory;
19
import org.openconcerto.sql.element.SQLElementDirectory;
-
 
20
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
20
import org.openconcerto.sql.model.DBFileCache;
21
import org.openconcerto.sql.model.DBFileCache;
21
import org.openconcerto.sql.model.DBItemFileCache;
22
import org.openconcerto.sql.model.DBItemFileCache;
22
import org.openconcerto.sql.model.DBRoot;
23
import org.openconcerto.sql.model.DBRoot;
-
 
24
import org.openconcerto.sql.model.SQLDataSource;
23
import org.openconcerto.sql.model.SQLField;
25
import org.openconcerto.sql.model.SQLField;
24
import org.openconcerto.sql.model.SQLName;
26
import org.openconcerto.sql.model.SQLName;
25
import org.openconcerto.sql.model.SQLRow;
27
import org.openconcerto.sql.model.SQLRow;
26
import org.openconcerto.sql.model.SQLRowListRSH;
28
import org.openconcerto.sql.model.SQLRowListRSH;
27
import org.openconcerto.sql.model.SQLRowValues;
29
import org.openconcerto.sql.model.SQLRowValues;
28
import org.openconcerto.sql.model.SQLSelect;
30
import org.openconcerto.sql.model.SQLSelect;
29
import org.openconcerto.sql.model.SQLSyntax;
31
import org.openconcerto.sql.model.SQLSyntax;
-
 
32
import org.openconcerto.sql.model.SQLSystem;
30
import org.openconcerto.sql.model.SQLTable;
33
import org.openconcerto.sql.model.SQLTable;
31
import org.openconcerto.sql.model.Where;
34
import org.openconcerto.sql.model.Where;
32
import org.openconcerto.sql.model.graph.DirectedEdge;
35
import org.openconcerto.sql.model.graph.DirectedEdge;
33
import org.openconcerto.sql.utils.AlterTable;
36
import org.openconcerto.sql.utils.AlterTable;
34
import org.openconcerto.sql.utils.ChangeTable;
37
import org.openconcerto.sql.utils.ChangeTable;
35
import org.openconcerto.sql.utils.DropTable;
38
import org.openconcerto.sql.utils.DropTable;
36
import org.openconcerto.sql.utils.SQLCreateTable;
39
import org.openconcerto.sql.utils.SQLCreateTable;
37
import org.openconcerto.sql.utils.SQLUtils;
40
import org.openconcerto.sql.utils.SQLUtils;
38
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
41
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
39
import org.openconcerto.sql.view.list.RowAction;
42
import org.openconcerto.sql.view.list.RowAction;
40
import org.openconcerto.task.config.ComptaBasePropsConfiguration;
-
 
41
import org.openconcerto.utils.CollectionUtils;
43
import org.openconcerto.utils.CollectionUtils;
42
import org.openconcerto.utils.ExceptionHandler;
44
import org.openconcerto.utils.ExceptionHandler;
-
 
45
import org.openconcerto.utils.FileUtils;
43
import org.openconcerto.utils.StringUtils;
46
import org.openconcerto.utils.StringUtils;
44
import org.openconcerto.utils.Tuple2;
47
import org.openconcerto.utils.Tuple2;
45
import org.openconcerto.utils.cc.IClosure;
48
import org.openconcerto.utils.cc.IClosure;
46
import org.openconcerto.utils.cc.IdentityHashSet;
49
import org.openconcerto.utils.cc.IdentityHashSet;
-
 
50
import org.openconcerto.utils.cc.IdentitySet;
47
 
51
 
48
import java.io.File;
52
import java.io.File;
49
import java.io.FileFilter;
53
import java.io.FileFilter;
50
import java.io.IOException;
54
import java.io.IOException;
51
import java.io.InputStream;
55
import java.io.InputStream;
Line 58... Line 62...
58
import java.util.HashSet;
62
import java.util.HashSet;
59
import java.util.LinkedHashMap;
63
import java.util.LinkedHashMap;
60
import java.util.LinkedHashSet;
64
import java.util.LinkedHashSet;
61
import java.util.List;
65
import java.util.List;
62
import java.util.Map;
66
import java.util.Map;
63
import java.util.Set;
-
 
64
import java.util.Map.Entry;
67
import java.util.Map.Entry;
-
 
68
import java.util.Set;
65
import java.util.logging.Logger;
69
import java.util.logging.Logger;
66
import java.util.prefs.BackingStoreException;
-
 
67
import java.util.prefs.Preferences;
70
import java.util.prefs.Preferences;
68
 
71
 
69
import javax.swing.JMenuItem;
72
import javax.swing.JMenuItem;
70
import javax.swing.SwingUtilities;
73
import javax.swing.SwingUtilities;
71
 
74
 
Line 81... Line 84...
81
public class ModuleManager {
84
public class ModuleManager {
82
 
85
 
83
    private static final Logger L = Logger.getLogger(ModuleManager.class.getPackage().getName());
86
    private static final Logger L = Logger.getLogger(ModuleManager.class.getPackage().getName());
84
 
87
 
85
    private static final int MIN_VERSION = 0;
88
    private static final int MIN_VERSION = 0;
86
    private static final int NO_VERSION = MIN_VERSION - 1;
-
 
87
    private static final String MODULE_COLNAME = "MODULE_NAME";
89
    private static final String MODULE_COLNAME = "MODULE_NAME";
88
    private static final String MODULE_VERSION_COLNAME = "MODULE_VERSION";
90
    private static final String MODULE_VERSION_COLNAME = "MODULE_VERSION";
89
    private static final String TABLE_COLNAME = "TABLE";
91
    private static final String TABLE_COLNAME = "TABLE";
90
    private static final String FIELD_COLNAME = "FIELD";
92
    private static final String FIELD_COLNAME = "FIELD";
91
    private static final String ISKEY_COLNAME = "KEY";
93
    private static final String ISKEY_COLNAME = "KEY";
Line 103... Line 105...
103
    private final Map<String, AbstractModule> runningModules;
105
    private final Map<String, AbstractModule> runningModules;
104
    private final Map<String, Collection<SQLElement>> modulesElements;
106
    private final Map<String, Collection<SQLElement>> modulesElements;
105
    private final Map<String, ComponentsContext> modulesComponents;
107
    private final Map<String, ComponentsContext> modulesComponents;
106
    private final DirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>> dependencyGraph;
108
    private final DirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>> dependencyGraph;
107
 
109
 
-
 
110
    private DBRoot root;
-
 
111
    private Configuration conf;
-
 
112
 
108
    public ModuleManager() {
113
    public ModuleManager() {
109
        this.factories = new HashMap<String, ModuleFactory>();
114
        this.factories = new HashMap<String, ModuleFactory>();
110
        this.runningModules = new HashMap<String, AbstractModule>();
115
        this.runningModules = new HashMap<String, AbstractModule>();
111
        this.dependencyGraph = new SimpleDirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>>(new EdgeFactory<ModuleFactory, DirectedEdge<ModuleFactory>>() {
116
        this.dependencyGraph = new SimpleDirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>>(new EdgeFactory<ModuleFactory, DirectedEdge<ModuleFactory>>() {
112
            @Override
117
            @Override
Line 114... Line 119...
114
                return new DirectedEdge<ModuleFactory>(sourceVertex, targetVertex);
119
                return new DirectedEdge<ModuleFactory>(sourceVertex, targetVertex);
115
            }
120
            }
116
        });
121
        });
117
        this.modulesElements = new HashMap<String, Collection<SQLElement>>();
122
        this.modulesElements = new HashMap<String, Collection<SQLElement>>();
118
        this.modulesComponents = new HashMap<String, ComponentsContext>();
123
        this.modulesComponents = new HashMap<String, ComponentsContext>();
-
 
124
 
-
 
125
        this.root = null;
-
 
126
        this.conf = null;
119
    }
127
    }
120
 
128
 
121
    // *** factories (thread-safe)
129
    // *** factories (thread-safe)
122
 
130
 
123
    public final int addFactories(final File dir) {
131
    public final int addFactories(final File dir) {
124
        if (!dir.exists()) {
132
        if (!dir.exists()) {
125
            System.err.println("Warning: module factory directory not found: " + dir.getAbsolutePath());
133
            L.warning("Module factory directory not found: " + dir.getAbsolutePath());
126
            return 0;
134
            return 0;
127
        }
135
        }
128
        final File[] jars = dir.listFiles(new FileFilter() {
136
        final File[] jars = dir.listFiles(new FileFilter() {
129
            @Override
137
            @Override
130
            public boolean accept(File f) {
138
            public boolean accept(File f) {
Line 136... Line 144...
136
            for (final File jar : jars) {
144
            for (final File jar : jars) {
137
                try {
145
                try {
138
                    this.addFactory(new JarModuleFactory(jar));
146
                    this.addFactory(new JarModuleFactory(jar));
139
                    i++;
147
                    i++;
140
                } catch (Exception e) {
148
                } catch (Exception e) {
141
                    System.err.println("Couldn't add " + jar);
149
                    L.warning("Couldn't add " + jar);
142
                    e.printStackTrace();
150
                    e.printStackTrace();
143
                }
151
                }
144
            }
152
            }
145
        }
153
        }
146
        return i;
154
        return i;
Line 166... Line 174...
166
        return this.addFactory(f, true, persistent);
174
        return this.addFactory(f, true, persistent);
167
    }
175
    }
168
 
176
 
169
    private final String addFactory(final ModuleFactory f, final boolean start, final boolean persistent) {
177
    private final String addFactory(final ModuleFactory f, final boolean start, final boolean persistent) {
170
        synchronized (this.factories) {
178
        synchronized (this.factories) {
171
            this.factories.put(f.getID(), f);
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);
172
        }
182
        }
173
        if (start)
183
        if (start)
174
            this.invoke(new IClosure<ModuleManager>() {
184
            this.invoke(new IClosure<ModuleManager>() {
175
                @Override
185
                @Override
176
                public void executeChecked(ModuleManager input) {
186
                public void executeChecked(ModuleManager input) {
Line 183... Line 193...
183
            });
193
            });
184
        return f.getID();
194
        return f.getID();
185
    }
195
    }
186
 
196
 
187
    public final Map<String, ModuleFactory> getFactories() {
197
    public final Map<String, ModuleFactory> getFactories() {
-
 
198
        synchronized (this.factories) {
188
        return Collections.unmodifiableMap(this.factories);
199
            return new HashMap<String, ModuleFactory>(this.factories);
-
 
200
        }
189
    }
201
    }
190
 
202
 
191
    private ModuleFactory getFactory(final String id) {
203
    private ModuleFactory getFactory(final String id) {
-
 
204
        final ModuleFactory res;
-
 
205
        synchronized (this.factories) {
192
        final ModuleFactory res = this.factories.get(id);
206
            res = this.factories.get(id);
-
 
207
        }
193
        if (res == null)
208
        if (res == null)
194
            throw new IllegalArgumentException("No factory for " + id);
209
            throw new IllegalArgumentException("No factory for " + id);
195
        return res;
210
        return res;
196
    }
211
    }
197
 
212
 
Line 233... Line 248...
233
    }
248
    }
234
 
249
 
235
    // *** modules (in EDT)
250
    // *** modules (in EDT)
236
 
251
 
237
    /**
252
    /**
238
     * Call the passed closure at a time when modules can be started. In particular the
253
     * Call the passed closure at a time when modules can be started. In particular this manager has
239
     * {@link MainFrame#getInstance() main frame} has been created.
254
     * been set up and the {@link MainFrame#getInstance() main frame} has been created.
-
 
255
     * 
-
 
256
     * @param c the closure to execute.
240
     */
257
     */
241
    public void invoke(final IClosure<ModuleManager> c) {
258
    public void invoke(final IClosure<ModuleManager> c) {
242
        MainFrame.invoke(new Runnable() {
259
        MainFrame.invoke(new Runnable() {
243
            @Override
260
            @Override
244
            public void run() {
261
            public void run() {
Line 249... Line 266...
249
 
266
 
250
    // call registerSQLElements() for any installed modules that might need it
267
    // call registerSQLElements() for any installed modules that might need it
251
    // (e.g. if a module created a child table, as long as the table is in the database it needs to
268
    // (e.g. if a module created a child table, as long as the table is in the database it needs to
252
    // be archived along its parent)
269
    // be archived along its parent)
253
    private void registerRequiredModules() throws Exception {
270
    private void registerRequiredModules() throws Exception {
254
        assert SwingUtilities.isEventDispatchThread();
-
 
255
        final List<String> modulesToStart = new ArrayList<String>();
271
        final List<String> modulesToStart = new ArrayList<String>();
256
        try {
272
        try {
-
 
273
            final Map<String, ModuleFactory> factories = this.getFactories();
257
            for (final Entry<String, ModuleVersion> e : this.getDBInstalledModules().entrySet()) {
274
            for (final Entry<String, ModuleVersion> e : this.getDBInstalledModules().entrySet()) {
258
                final String moduleID = e.getKey();
275
                final String moduleID = e.getKey();
259
                // modules that just add non-key fields are not required
276
                // modules that just add non-key fields are not required
260
                if (this.areElementsNeeded(moduleID)) {
277
                if (this.areElementsNeeded(moduleID)) {
261
                    final ModuleFactory moduleFactory = this.getFactories().get(moduleID);
278
                    final ModuleFactory moduleFactory = factories.get(moduleID);
262
                    final String error;
279
                    final String error;
263
                    if (moduleFactory == null)
280
                    if (moduleFactory == null)
264
                        error = "Module '" + moduleID + "' non disponible.";
281
                        error = "Module '" + moduleID + "' non disponible.";
265
                    else if (!moduleFactory.getVersion().equals(e.getValue()))
282
                    else if (!moduleFactory.getVersion().equals(e.getValue()))
266
                        error = "Mauvaise version pour '" + moduleID + "'. La version " + moduleFactory.getVersion() + " est disponible mais " + e.getValue() + " est requise.";
283
                        error = "Mauvaise version pour '" + moduleID + "'. La version " + moduleFactory.getVersion() + " est disponible mais " + e.getValue() + " est requise.";
267
                    // check canCreate() after since it's more efficient
284
                    // check canCreate() after since it's more efficient
268
                    else
285
                    else
269
                        error = null;
286
                        error = null;
270
                    if (error != null) {
287
                    if (error != null) {
271
                        // TODO open GUI to resolve the issue
288
                        // TODO open GUI to resolve the issue
272
                        ExceptionHandler.handle(error);
289
                        throw new Exception(error);
273
                        return;
-
 
274
                    } else {
290
                    } else {
275
                        modulesToStart.add(moduleID);
291
                        modulesToStart.add(moduleID);
276
                    }
292
                    }
277
                }
293
                }
278
            }
294
            }
279
        } catch (Exception e) {
295
        } catch (Exception e) {
280
            ExceptionHandler.die("Impossible de déterminer les modules requis", e);
296
            throw new Exception("Impossible de déterminer les modules requis", e);
281
            return;
-
 
282
        }
297
        }
283
        final Tuple2<Map<String, AbstractModule>, Set<String>> modules = this.createModules(modulesToStart, false);
298
        final Tuple2<Map<String, AbstractModule>, Set<String>> modules = this.createModules(modulesToStart, false, true);
284
        if (modules.get1().size() > 0)
299
        if (modules.get1().size() > 0)
285
            ExceptionHandler.die("Impossible de créer les modules " + modules.get1());
300
            throw new Exception("Impossible de créer les modules " + modules.get1());
286
        for (final AbstractModule m : modules.get0().values())
301
        for (final AbstractModule m : modules.get0().values())
287
            this.registerSQLElements(m);
302
            this.registerSQLElements(m);
288
    }
303
    }
289
 
304
 
290
    /**
305
    /**
291
     * Initialize the module manager.
306
     * Initialise the module manager.
292
     * 
307
     * 
-
 
308
     * @param root the root where the modules install.
293
     * @return the exception, if any, thrown when starting previously running modules.
309
     * @param conf the configuration the modules change.
294
     * @throws Exception if required modules couldn't be registered.
310
     * @throws Exception if required modules couldn't be registered.
295
     */
311
     */
-
 
312
    public synchronized final void setup(final DBRoot root, final Configuration conf) throws Exception {
-
 
313
        if (root == null || conf == null)
296
    public final Exception setup() throws Exception {
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";
297
        this.registerRequiredModules();
319
        this.root = root;
-
 
320
        this.conf = conf;
298
        try {
321
        try {
299
            this.startPreviouslyRunningModules();
322
            this.registerRequiredModules();
300
            return null;
-
 
301
        } catch (Exception e) {
323
        } catch (Exception e) {
-
 
324
            // allow setup() to be called again
-
 
325
            this.root = null;
-
 
326
            this.conf = null;
302
            return e;
327
            throw e;
303
        }
328
        }
-
 
329
        assert this.runningModules.isEmpty() && this.modulesComponents.isEmpty() : "registerRequiredModules() should not start modules";
304
    }
330
    }
305
 
331
 
306
    private Preferences getPrefs() {
332
    private Preferences getPrefs() {
307
        // modules are installed per business entity (perhaps we could add a per user option, i.e.
333
        // modules are installed per business entity (perhaps we could add a per user option, i.e.
308
        // for all businesses of all databases)
334
        // for all businesses of all databases)
309
        final StringBuilder path = new StringBuilder(32);
335
        final StringBuilder path = new StringBuilder(32);
310
        for (final String item : DBFileCache.getJDBCAncestorNames(Configuration.getInstance().getRoot(), true)) {
336
        for (final String item : DBFileCache.getJDBCAncestorNames(getRoot(), true)) {
311
            path.append(StringUtils.getBoundedLengthString(DBItemFileCache.encode(item), Preferences.MAX_NAME_LENGTH));
337
            path.append(StringUtils.getBoundedLengthString(DBItemFileCache.encode(item), Preferences.MAX_NAME_LENGTH));
312
            path.append('/');
338
            path.append('/');
313
        }
339
        }
314
        // path must not end with '/'
340
        // path must not end with '/'
315
        path.setLength(path.length() - 1);
341
        path.setLength(path.length() - 1);
Line 318... Line 344...
318
 
344
 
319
    private Preferences getRunningIDsPrefs() {
345
    private Preferences getRunningIDsPrefs() {
320
        return getPrefs().node("toRun");
346
        return getPrefs().node("toRun");
321
    }
347
    }
322
 
348
 
323
    private Preferences getInstalledPrefs() {
-
 
324
        return getPrefs().node("installed");
-
 
325
    }
-
 
326
 
-
 
327
    protected final boolean isModuleInstalledLocally(String id) {
349
    protected final boolean isModuleInstalledLocally(String id) {
328
        return getInstalledPrefs().getLong(id, NO_VERSION) != NO_VERSION;
350
        return getLocalVersionFile(id).exists();
329
    }
351
    }
330
 
352
 
331
    protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
353
    protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
332
        final long v = getInstalledPrefs().getLong(id, NO_VERSION);
354
        final File versionFile = getLocalVersionFile(id);
-
 
355
        if (versionFile.exists()) {
-
 
356
            try {
333
        return v == NO_VERSION ? null : new ModuleVersion(v);
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
        }
334
    }
364
    }
335
 
365
 
336
    public final Collection<String> getModulesInstalledLocally() {
366
    public final Collection<String> getModulesInstalledLocally() {
337
        try {
-
 
338
            return Arrays.asList(getInstalledPrefs().keys());
367
        return getModulesVersionInstalledLocally().keySet();
339
        } catch (BackingStoreException e) {
-
 
340
            throw new IllegalStateException("Couldn't fetch installed preferences", e);
-
 
341
        }
-
 
342
    }
368
    }
343
 
369
 
344
    public final Map<String, ModuleVersion> getModulesVersionInstalledLocally() {
370
    public final Map<String, ModuleVersion> getModulesVersionInstalledLocally() {
345
        final Preferences prefs = getInstalledPrefs();
-
 
346
        final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
371
        final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
347
        try {
372
        final File dir = getLocalDirectory();
-
 
373
        for (final File d : dir.listFiles()) {
348
            for (final String key : prefs.keys())
374
            final String id = d.getName();
349
                res.put(key, new ModuleVersion(prefs.getLong(key, NO_VERSION)));
375
            final ModuleVersion version = getModuleVersionInstalledLocally(id);
350
        } catch (BackingStoreException e) {
376
            if (version != null)
351
            throw new IllegalStateException("Couldn't fetch installed preferences", e);
377
                res.put(id, version);
352
        }
378
        }
353
        return res;
379
        return res;
354
    }
380
    }
355
 
381
 
356
    private void setModuleInstalledLocally(ModuleFactory f, boolean b) {
382
    private void setModuleInstalledLocally(ModuleFactory f, boolean b) {
-
 
383
        try {
357
        if (b) {
384
            if (b) {
358
            final ModuleVersion vers = f.getVersion();
385
                final ModuleVersion vers = f.getVersion();
359
            if (vers.getMerged() < MIN_VERSION)
386
                if (vers.getMerged() < MIN_VERSION)
360
                throw new IllegalStateException("Invalid version : " + vers);
387
                    throw new IllegalStateException("Invalid version : " + vers);
-
 
388
                final File versionFile = getLocalVersionFile(f.getID());
-
 
389
                FileUtils.mkdir_p(versionFile.getParentFile());
361
            getInstalledPrefs().putLong(f.getID(), vers.getMerged());
390
                FileUtils.write(String.valueOf(vers.getMerged()), versionFile);
362
        } else {
391
            } else {
-
 
392
                // perhaps add a parameter to only remove the versionFile
363
            getInstalledPrefs().remove(f.getID());
393
                FileUtils.rm_R(getLocalDirectory(f.getID()));
-
 
394
            }
-
 
395
        } catch (IOException e) {
-
 
396
            throw new IllegalStateException("Couldn't change installed status of " + f, e);
364
        }
397
        }
365
    }
398
    }
366
 
399
 
367
    private SQLTable getInstalledTable(final DBRoot r) throws SQLException {
400
    private SQLTable getInstalledTable(final DBRoot r) throws SQLException {
368
        if (!r.contains(FWK_MODULE_TABLENAME)) {
401
        if (!r.contains(FWK_MODULE_TABLENAME)) {
Line 379... Line 412...
379
            createTable.addColumn(ISKEY_COLNAME, "boolean NULL");
412
            createTable.addColumn(ISKEY_COLNAME, "boolean NULL");
380
            createTable.addColumn(MODULE_VERSION_COLNAME, "bigint NOT NULL");
413
            createTable.addColumn(MODULE_VERSION_COLNAME, "bigint NOT NULL");
381
 
414
 
382
            createTable.addUniqueConstraint("uniqModule", Arrays.asList(MODULE_COLNAME, TABLE_COLNAME, FIELD_COLNAME));
415
            createTable.addUniqueConstraint("uniqModule", Arrays.asList(MODULE_COLNAME, TABLE_COLNAME, FIELD_COLNAME));
383
 
416
 
384
            SQLUtils.executeAtomic(Configuration.getInstance().getSystemRoot().getDataSource(), new SQLFactory<Object>() {
417
            SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
385
                @Override
418
                @Override
386
                public Object create() throws SQLException {
419
                public Object create() throws SQLException {
387
                    r.getDBSystemRoot().getDataSource().execute(createTable.asString());
420
                    r.getDBSystemRoot().getDataSource().execute(createTable.asString());
388
                    r.getSchema().updateVersion();
421
                    r.getSchema().updateVersion();
389
                    SQLTable.setUndefID(r.getSchema(), createTable.getName(), null);
422
                    SQLTable.setUndefID(r.getSchema(), createTable.getName(), null);
Line 393... Line 426...
393
            r.refetch();
426
            r.refetch();
394
        }
427
        }
395
        return r.getTable(FWK_MODULE_TABLENAME);
428
        return r.getTable(FWK_MODULE_TABLENAME);
396
    }
429
    }
397
 
430
 
398
    private DBRoot getRoot() {
431
    protected final DBRoot getRoot() {
-
 
432
        return this.root;
-
 
433
    }
-
 
434
 
-
 
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() {
399
        return ((ComptaBasePropsConfiguration) Configuration.getInstance()).getRootSociete();
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");
400
    }
457
    }
401
 
458
 
402
    public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
459
    public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
403
        return getDBInstalledModules(id).get(id);
460
        return getDBInstalledModules(id).get(id);
404
    }
461
    }
Line 529... Line 586...
529
        final Where keyCreated = Where.isNotNull(installedTable.getField(FIELD_COLNAME)).and(new Where(installedTable.getField(ISKEY_COLNAME), "=", Boolean.TRUE));
586
        final Where keyCreated = Where.isNotNull(installedTable.getField(FIELD_COLNAME)).and(new Where(installedTable.getField(ISKEY_COLNAME), "=", Boolean.TRUE));
530
        sel.setWhere(idW.and(tableCreated.or(keyCreated)));
587
        sel.setWhere(idW.and(tableCreated.or(keyCreated)));
531
        return (Boolean) installedTable.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
588
        return (Boolean) installedTable.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
532
    }
589
    }
533
 
590
 
534
    private void install(final AbstractModule module) throws SQLException {
591
    private void install(final AbstractModule module) throws Exception {
535
        final ModuleFactory factory = module.getFactory();
592
        final ModuleFactory factory = module.getFactory();
536
        if (!isModuleInstalledLocally(factory.getID())) {
593
        final ModuleVersion localVersion = getModuleVersionInstalledLocally(factory.getID());
537
            final ModuleVersion lastInstalledVersion = getDBInstalledModuleVersion(factory.getID());
594
        final ModuleVersion lastInstalledVersion = getDBInstalledModuleVersion(factory.getID());
-
 
595
        final ModuleVersion moduleVersion = module.getFactory().getVersion();
538
            if (lastInstalledVersion != null && module.getFactory().getVersion().compareTo(lastInstalledVersion) < 0)
596
        if (lastInstalledVersion != null && moduleVersion.compareTo(lastInstalledVersion) < 0)
539
                throw new IllegalArgumentException("Module older than the one installed in the DB : " + module.getFactory().getVersion() + " < " + lastInstalledVersion);
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 {
540
            SQLUtils.executeAtomic(Configuration.getInstance().getSystemRoot().getDataSource(), new SQLFactory<Object>() {
622
                SQLUtils.executeAtomic(getDS(), new ConnectionHandlerNoSetup<Object, IOException>() {
541
                @Override
623
                    @Override
542
                public Object create() throws SQLException {
624
                    public Object handle(SQLDataSource ds) throws SQLException, IOException {
543
                    final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
625
                        final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
544
                    final DBContext ctxt = new DBContext(lastInstalledVersion, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
626
                        final DBContext ctxt = new DBContext(localDir, localVersion, getRoot(), lastInstalledVersion, alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
545
                    module.install(ctxt);
-
 
546
 
-
 
547
                    // install local
627
                        // install local
548
                    setModuleInstalledLocally(factory, true);
628
                        module.install(ctxt);
-
 
629
                        if (!localDir.exists())
-
 
630
                            throw new IOException("Modules shouldn't remove their directory");
549
                    // install in DB
631
                        // install in DB
550
                    ctxt.execute();
632
                        ctxt.execute();
551
                    updateModuleFields(factory, ctxt);
633
                        updateModuleFields(factory, ctxt);
552
 
-
 
553
                    return null;
634
                        return null;
554
                }
635
                    }
555
            });
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);
-
 
652
                }
-
 
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);
556
        }
660
        }
-
 
661
        assert moduleVersion.equals(getModuleVersionInstalledLocally(factory.getID())) && moduleVersion.equals(getDBInstalledModuleVersion(factory.getID()));
557
    }
662
    }
558
 
663
 
559
    private void registerSQLElements(final AbstractModule module) {
664
    private void registerSQLElements(final AbstractModule module) {
560
        final String id = module.getFactory().getID();
665
        final String id = module.getFactory().getID();
-
 
666
        synchronized (this.modulesElements) {
561
        if (!this.modulesElements.containsKey(id)) {
667
            if (!this.modulesElements.containsKey(id)) {
562
            final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
668
                final SQLElementDirectory dir = getDirectory();
563
            final Map<SQLTable, SQLElement> beforeElements = new HashMap<SQLTable, SQLElement>(dir.getElementsMap());
669
                final Map<SQLTable, SQLElement> beforeElements = new HashMap<SQLTable, SQLElement>(dir.getElementsMap());
564
            module.setupElements(dir);
670
                module.setupElements(dir);
565
            final Collection<SQLElement> elements = new ArrayList<SQLElement>();
671
                final Collection<SQLElement> elements = new ArrayList<SQLElement>();
-
 
672
                // use IdentitySet so as not to call equals() since it triggers initFF()
566
            final Collection<SQLElement> newElements = CollectionUtils.substract(new IdentityHashSet<SQLElement>(dir.getElements()), new IdentityHashSet<SQLElement>(beforeElements.values()));
673
                final IdentitySet<SQLElement> beforeElementsSet = new IdentityHashSet<SQLElement>(beforeElements.values());
567
            for (final SQLElement elem : newElements) {
674
                for (final SQLElement elem : dir.getElements()) {
-
 
675
                    if (!beforeElementsSet.contains(elem)) {
568
                // don't let elements be replaced (it's tricky to restore in unregister())
676
                        // don't let elements be replaced (it's tricky to restore in unregister())
569
                if (beforeElements.containsKey(elem.getTable())) {
677
                        if (beforeElements.containsKey(elem.getTable())) {
570
                    L.warning("Trying to replace element for " + elem.getTable() + " with " + elem);
678
                            L.warning("Trying to replace element for " + elem.getTable() + " with " + elem);
571
                    dir.addSQLElement(beforeElements.get(elem.getTable()));
679
                            dir.addSQLElement(beforeElements.get(elem.getTable()));
572
                } else {
680
                        } else {
573
                    elements.add(elem);
681
                            elements.add(elem);
574
                }
682
                        }
575
            }
683
                    }
-
 
684
                }
576
            this.modulesElements.put(id, elements);
685
                this.modulesElements.put(id, elements);
577
        }
686
            }
578
    }
687
        }
-
 
688
    }
579
 
689
 
580
    private void setupComponents(final AbstractModule module) throws SQLException {
690
    private void setupComponents(final AbstractModule module) throws SQLException {
581
        final String id = module.getFactory().getID();
691
        final String id = module.getFactory().getID();
582
        if (!this.modulesComponents.containsKey(id)) {
692
        if (!this.modulesComponents.containsKey(id)) {
583
            final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
693
            final SQLElementDirectory dir = getDirectory();
584
            final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(id);
694
            final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(id);
585
            final ComponentsContext ctxt = new ComponentsContext(dir, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
695
            final ComponentsContext ctxt = new ComponentsContext(dir, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
586
            module.setupComponents(ctxt);
696
            module.setupComponents(ctxt);
587
            this.modulesComponents.put(id, ctxt);
697
            this.modulesComponents.put(id, ctxt);
588
        }
698
        }
589
    }
699
    }
590
 
700
 
591
    protected final void startPreviouslyRunningModules() throws Exception {
701
    public final void startPreviouslyRunningModules() throws Exception {
592
        final List<String> ids = Arrays.asList(getRunningIDsPrefs().keys());
702
        final List<String> ids = Arrays.asList(getRunningIDsPrefs().keys());
593
        startModules(ids);
703
        startModules(ids);
594
    }
704
    }
595
 
705
 
596
    public final boolean startModule(final String id) throws Exception {
706
    public final boolean startModule(final String id) throws Exception {
Line 613... Line 723...
613
        }
723
        }
614
        return res;
724
        return res;
615
    }
725
    }
616
 
726
 
617
    private final Set<String> startModules(final Collection<String> ids) throws Exception {
727
    private final Set<String> startModules(final Collection<String> ids) throws Exception {
618
        return this.createModules(ids, true).get1();
728
        return this.createModules(ids, true, false).get1();
619
    }
729
    }
620
 
730
 
621
    // modules created, and the ones that couldn't
731
    // modules created, and the ones that couldn't
622
    // i.e. if a module was already created it's in neither
732
    // i.e. if a module was already created it's in neither
623
    private final Tuple2<Map<String, AbstractModule>, Set<String>> createModules(final Collection<String> ids, final boolean start) throws Exception {
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
624
        assert SwingUtilities.isEventDispatchThread();
735
        assert SwingUtilities.isEventDispatchThread() || inSetup && this.runningModules.isEmpty();
625
        // add currently running modules so that ModuleFactory can use them
736
        // add currently running modules so that ModuleFactory can use them
626
        final Map<String, AbstractModule> modules = new LinkedHashMap<String, AbstractModule>(this.runningModules);
737
        final Map<String, AbstractModule> modules = inSetup ? new LinkedHashMap<String, AbstractModule>(ids.size() * 2) : new LinkedHashMap<String, AbstractModule>(this.runningModules);
627
        final Set<String> cannotCreate = new HashSet<String>();
738
        final Set<String> cannotCreate = new HashSet<String>();
628
        final LinkedHashMap<ModuleFactory, Boolean> map = new LinkedHashMap<ModuleFactory, Boolean>();
739
        final LinkedHashMap<ModuleFactory, Boolean> map = new LinkedHashMap<ModuleFactory, Boolean>();
629
        synchronized (this.factories) {
740
        synchronized (this.factories) {
630
            for (final String id : ids) {
741
            for (final String id : ids) {
631
                final ModuleFactory f = getFactory(id);
742
                final ModuleFactory f = getFactory(id);
Line 638... Line 749...
638
                    cannotCreate.add(id);
749
                    cannotCreate.add(id);
639
                }
750
                }
640
            }
751
            }
641
        }
752
        }
642
        // only keep modules created by this method
753
        // only keep modules created by this method
-
 
754
        if (!inSetup)
643
        modules.keySet().removeAll(this.runningModules.keySet());
755
            modules.keySet().removeAll(this.runningModules.keySet());
644
 
756
 
645
        if (start) {
757
        if (start) {
646
            for (final AbstractModule module : modules.values())
758
            for (final AbstractModule module : modules.values())
647
                startModule(module);
759
                startModule(module);
Line 665... Line 777...
665
            }
777
            }
666
            try {
778
            try {
667
                final InputStream labels = module.getClass().getResourceAsStream("labels.xml");
779
                final InputStream labels = module.getClass().getResourceAsStream("labels.xml");
668
                if (labels != null) {
780
                if (labels != null) {
669
                    try {
781
                    try {
670
                        Configuration.getInstance().getTranslator().load(getRoot(), labels);
782
                        getConf().getTranslator().load(getRoot(), labels);
671
                    } finally {
783
                    } finally {
672
                        labels.close();
784
                        labels.close();
673
                    }
785
                    }
674
                }
786
                }
675
                this.registerSQLElements(module);
787
                this.registerSQLElements(module);
Line 733... Line 845...
733
        assert !this.isModuleRunning(id);
845
        assert !this.isModuleRunning(id);
734
    }
846
    }
735
 
847
 
736
    private void unregisterSQLElements(final AbstractModule module) {
848
    private void unregisterSQLElements(final AbstractModule module) {
737
        final String id = module.getFactory().getID();
849
        final String id = module.getFactory().getID();
-
 
850
        synchronized (this.modulesElements) {
738
        if (this.modulesElements.containsKey(id)) {
851
            if (this.modulesElements.containsKey(id)) {
739
            final Collection<SQLElement> elements = this.modulesElements.remove(id);
852
                final Collection<SQLElement> elements = this.modulesElements.remove(id);
740
            final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
853
                final SQLElementDirectory dir = getDirectory();
741
            for (final SQLElement elem : elements)
854
                for (final SQLElement elem : elements)
742
                dir.removeSQLElement(elem);
855
                    dir.removeSQLElement(elem);
743
        }
856
            }
744
    }
857
        }
-
 
858
    }
745
 
859
 
746
    private void tearDownComponents(final AbstractModule module) {
860
    private void tearDownComponents(final AbstractModule module) {
747
        final String id = module.getFactory().getID();
861
        final String id = module.getFactory().getID();
748
        if (this.modulesComponents.containsKey(id)) {
862
        if (this.modulesComponents.containsKey(id)) {
749
            final ComponentsContext ctxt = this.modulesComponents.remove(id);
863
            final ComponentsContext ctxt = this.modulesComponents.remove(id);
Line 891... Line 1005...
891
        if (dbVersion != null && !moduleFactory.getVersion().equals(dbVersion))
1005
        if (dbVersion != null && !moduleFactory.getVersion().equals(dbVersion))
892
            throw new IllegalStateException("DB version not equal : " + dbVersion);
1006
            throw new IllegalStateException("DB version not equal : " + dbVersion);
893
 
1007
 
894
        final AbstractModule module;
1008
        final AbstractModule module;
895
        if (!this.isModuleRunning(id)) {
1009
        if (!this.isModuleRunning(id)) {
896
            module = this.createModules(Collections.singleton(id), false).get0().get(id);
1010
            module = this.createModules(Collections.singleton(id), false, false).get0().get(id);
897
        } else {
1011
        } else {
898
            module = this.runningModules.get(id);
1012
            module = this.runningModules.get(id);
899
            this.stopModule(id, true);
1013
            this.stopModule(id, true);
900
        }
1014
        }
901
 
1015
 
902
        SQLUtils.executeAtomic(Configuration.getInstance().getSystemRoot().getDataSource(), new SQLFactory<Object>() {
1016
        SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
903
            @Override
1017
            @Override
904
            public Object create() throws SQLException {
1018
            public Object create() throws SQLException {
-
 
1019
                final DBRoot root = getRoot();
905
                module.uninstall();
1020
                module.uninstall(root);
906
                unregisterSQLElements(module);
1021
                unregisterSQLElements(module);
907
                setModuleInstalledLocally(module.getFactory(), false);
1022
                setModuleInstalledLocally(module.getFactory(), false);
908
 
1023
 
909
                // uninstall from DB
1024
                // uninstall from DB
910
                final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
1025
                final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
911
                final DBRoot root = getRoot();
-
 
912
                final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
1026
                final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
913
                final Set<String> tableNames = createdItems.get0();
1027
                final Set<String> tableNames = createdItems.get0();
914
                for (final SQLName field : createdItems.get1()) {
1028
                for (final SQLName field : createdItems.get1()) {
915
                    final SQLField f = root.getDesc(field, SQLField.class);
1029
                    final SQLField f = root.getDesc(field, SQLField.class);
916
                    // dropped by DROP TABLE
1030
                    // dropped by DROP TABLE