OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 151 | Rev 177 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 151 Rev 156
Line 21... Line 21...
21
import org.openconcerto.erp.modules.ModuleTableModel.ModuleRow;
21
import org.openconcerto.erp.modules.ModuleTableModel.ModuleRow;
22
import org.openconcerto.sql.Configuration;
22
import org.openconcerto.sql.Configuration;
23
import org.openconcerto.sql.TM;
23
import org.openconcerto.sql.TM;
24
import org.openconcerto.sql.element.SQLElement;
24
import org.openconcerto.sql.element.SQLElement;
25
import org.openconcerto.sql.element.SQLElementDirectory;
25
import org.openconcerto.sql.element.SQLElementDirectory;
-
 
26
import org.openconcerto.sql.element.SQLElementNamesFromXML;
26
import org.openconcerto.sql.model.AliasedTable;
27
import org.openconcerto.sql.model.AliasedTable;
27
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
28
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
28
import org.openconcerto.sql.model.DBFileCache;
29
import org.openconcerto.sql.model.DBFileCache;
29
import org.openconcerto.sql.model.DBItemFileCache;
30
import org.openconcerto.sql.model.DBItemFileCache;
30
import org.openconcerto.sql.model.DBRoot;
31
import org.openconcerto.sql.model.DBRoot;
Line 55... Line 56...
55
import org.openconcerto.sql.view.list.IListeAction;
56
import org.openconcerto.sql.view.list.IListeAction;
56
import org.openconcerto.ui.SwingThreadUtils;
57
import org.openconcerto.ui.SwingThreadUtils;
57
import org.openconcerto.utils.CollectionMap2.Mode;
58
import org.openconcerto.utils.CollectionMap2.Mode;
58
import org.openconcerto.utils.CollectionUtils;
59
import org.openconcerto.utils.CollectionUtils;
59
import org.openconcerto.utils.ExceptionHandler;
60
import org.openconcerto.utils.ExceptionHandler;
-
 
61
import org.openconcerto.utils.ExceptionUtils;
60
import org.openconcerto.utils.FileUtils;
62
import org.openconcerto.utils.FileUtils;
61
import org.openconcerto.utils.SetMap;
63
import org.openconcerto.utils.SetMap;
62
import org.openconcerto.utils.StringUtils;
64
import org.openconcerto.utils.StringUtils;
63
import org.openconcerto.utils.ThreadFactory;
65
import org.openconcerto.utils.ThreadFactory;
64
import org.openconcerto.utils.Tuple2;
66
import org.openconcerto.utils.Tuple2;
Line 75... Line 77...
75
import java.io.FileFilter;
77
import java.io.FileFilter;
76
import java.io.FileInputStream;
78
import java.io.FileInputStream;
77
import java.io.FileOutputStream;
79
import java.io.FileOutputStream;
78
import java.io.IOException;
80
import java.io.IOException;
79
import java.io.InputStream;
81
import java.io.InputStream;
-
 
82
import java.nio.charset.StandardCharsets;
-
 
83
import java.nio.file.Files;
-
 
84
import java.nio.file.Path;
-
 
85
import java.nio.file.StandardCopyOption;
80
import java.sql.SQLException;
86
import java.sql.SQLException;
81
import java.util.ArrayList;
87
import java.util.ArrayList;
82
import java.util.Arrays;
88
import java.util.Arrays;
83
import java.util.Collection;
89
import java.util.Collection;
84
import java.util.Collections;
90
import java.util.Collections;
Line 133... Line 139...
133
    @GuardedBy("ModuleManager.class")
139
    @GuardedBy("ModuleManager.class")
134
    private static ExecutorService exec = null;
140
    private static ExecutorService exec = null;
135
 
141
 
136
    private static synchronized final Executor getExec() {
142
    private static synchronized final Executor getExec() {
137
        if (exec == null)
143
        if (exec == null)
138
            exec = new ThreadPoolExecutor(0, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
144
            exec = new ThreadPoolExecutor(0, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory(ModuleManager.class.getSimpleName()
139
                    new ThreadFactory(ModuleManager.class.getSimpleName()
-
 
140
                            // not daemon since install() is not atomic
145
                    // not daemon since install() is not atomic
141
                            + " executor thread ", false));
146
                    + " executor thread ", false));
142
        return exec;
147
        return exec;
143
    }
148
    }
144
 
149
 
Line 170... Line 175...
170
    private static final String NEEDED_MODULE_COLNAME = "ID_MODULE_NEEDED";
175
    private static final String NEEDED_MODULE_COLNAME = "ID_MODULE_NEEDED";
171
    private static final String fileMutex = new String("modules");
176
    private static final String fileMutex = new String("modules");
172
 
177
 
173
    private static final Integer TO_INSTALL_VERSION = 1;
178
    private static final Integer TO_INSTALL_VERSION = 1;
174
 
179
 
175
    @GuardedBy("ModuleManager.class")
-
 
176
    private static ModuleManager instance = null;
180
    private static final String OLD_BACKUP_DIR_SUFFIX = ".backup";
177
 
-
 
178
    public static synchronized ModuleManager getInstance() {
181
    private static final String OLD_FAILED_DIR_SUFFIX = ".failed";
179
        if (instance == null)
-
 
180
            instance = new ModuleManager();
-
 
181
        return instance;
-
 
182
    }
-
 
183
 
-
 
184
    static synchronized void resetInstance() {
182
    private static final String BACKUP_DIR = "Install backup";
185
        if (instance != null) {
-
 
186
            for (final String id : instance.getRunningModules().keySet()) {
183
    private static final String FAILED_DIR = "Install failed";
187
                instance.stopModuleRecursively(id);
-
 
188
            }
-
 
189
            instance = null;
-
 
190
        }
-
 
191
    }
-
 
192
 
184
 
193
    // return true if the MainFrame is not displayable (or if there's none)
185
    // return true if the MainFrame is not displayable (or if there's none)
194
    static private boolean noDisplayableFrame() {
186
    static private boolean noDisplayableFrame() {
195
        final MainFrame mf = MainFrame.getInstance();
187
        final MainFrame mf = MainFrame.getInstance();
196
        if (mf == null)
188
        if (mf == null)
Line 503... Line 495...
503
        ModuleState toInstallTargetState = ModuleState.NOT_CREATED;
495
        ModuleState toInstallTargetState = ModuleState.NOT_CREATED;
504
        if (toInstallFile.exists()) {
496
        if (toInstallFile.exists()) {
505
            if (!toInstallFile.canRead() || !toInstallFile.isFile()) {
497
            if (!toInstallFile.canRead() || !toInstallFile.isFile()) {
506
                L.warning("Couldn't read " + toInstallFile);
498
                L.warning("Couldn't read " + toInstallFile);
507
            } else {
499
            } else {
508
                final XMLDecoder dec = new XMLDecoder(new FileInputStream(toInstallFile));
500
                try (final XMLDecoder dec = new XMLDecoder(new FileInputStream(toInstallFile))) {
509
                try {
-
 
510
                    final Number version = (Number) dec.readObject();
501
                    final Number version = (Number) dec.readObject();
511
                    if (version.intValue() != TO_INSTALL_VERSION.intValue())
502
                    if (version.intValue() != TO_INSTALL_VERSION.intValue())
512
                        throw new Exception("Version mismatch, expected " + TO_INSTALL_VERSION + " found " + version);
503
                        throw new Exception("Version mismatch, expected " + TO_INSTALL_VERSION + " found " + version);
513
                    final Date fileDate = (Date) dec.readObject();
504
                    final Date fileDate = (Date) dec.readObject();
514
                    @SuppressWarnings("unchecked")
505
                    @SuppressWarnings("unchecked")
Line 543... Line 534...
543
                    final File errorFile = FileUtils.addSuffix(toInstallFile, ".error");
534
                    final File errorFile = FileUtils.addSuffix(toInstallFile, ".error");
544
                    errorFile.delete();
535
                    errorFile.delete();
545
                    final boolean renamed = toInstallFile.renameTo(errorFile);
536
                    final boolean renamed = toInstallFile.renameTo(errorFile);
546
                    throw new Exception("Couldn't parse " + toInstallFile + " ; renamed : " + renamed, e);
537
                    throw new Exception("Couldn't parse " + toInstallFile + " ; renamed : " + renamed, e);
547
                } finally {
538
                } finally {
-
 
539
                    // Either the installation will succeed and we don't want to execute it again,
-
 
540
                    // or the installation will fail and we don't want to be caught in an endless
-
 
541
                    // loop (since there's no API to remove this file).
548
                    dec.close();
542
                    if (toInstallFile.exists() && !toInstallFile.delete())
-
 
543
                        throw new IOException("Couldn't delete " + toInstallFile);
549
                }
544
                }
550
            }
545
            }
551
        }
546
        }
552
        // handle upgrades : the old version was not uninstalled and thus still in
547
        // handle upgrades : the old version was not uninstalled and thus still in
553
        // dbRequiredModules, and the new version is in toInstall, so the DepSolver will error out
548
        // dbRequiredModules, and the new version is in toInstall, so the DepSolver will error out
Line 577... Line 572...
577
            } else {
572
            } else {
578
                // NO_CHANGE since they were just installed above
573
                // NO_CHANGE since they were just installed above
579
                this.createModules(userReferencesToInstall, NoChoicePredicate.NO_CHANGE, ModuleState.STARTED, persistent);
574
                this.createModules(userReferencesToInstall, NoChoicePredicate.NO_CHANGE, ModuleState.STARTED, persistent);
580
            }
575
            }
581
        }
576
        }
582
        if (toInstallFile.exists() && !toInstallFile.delete())
-
 
583
            throw new IOException("Couldn't delete " + toInstallFile);
-
 
584
        this.inited = true;
577
        this.inited = true;
585
    }
578
    }
586
 
579
 
587
    // whether module removal can exit the VM
580
    // whether module removal can exit the VM
588
    final void setExitAllowed(boolean exitAllowed) {
581
    final void setExitAllowed(boolean exitAllowed) {
Line 645... Line 638...
645
    private final Preferences getRequiredIDsPrefs() {
638
    private final Preferences getRequiredIDsPrefs() {
646
        return getDBPrefs().node("required");
639
        return getDBPrefs().node("required");
647
    }
640
    }
648
 
641
 
649
    protected final boolean isModuleInstalledLocally(String id) {
642
    protected final boolean isModuleInstalledLocally(String id) {
-
 
643
        final File versionFile = getLocalVersionFile(id);
650
        return getLocalVersionFile(id).exists();
644
        return versionFile != null && versionFile.exists();
651
    }
645
    }
652
 
646
 
653
    protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
647
    protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
654
        synchronized (fileMutex) {
648
        synchronized (fileMutex) {
655
            final File versionFile = getLocalVersionFile(id);
649
            final File versionFile = getLocalVersionFile(id);
656
            if (versionFile.exists()) {
650
            if (versionFile != null && versionFile.exists()) {
657
                try {
651
                try {
658
                    return new ModuleVersion(Long.valueOf(FileUtils.read(versionFile)));
652
                    return new ModuleVersion(Long.valueOf(FileUtils.read(versionFile)));
659
                } catch (IOException e) {
653
                } catch (IOException e) {
660
                    throw new IllegalStateException("Couldn't get installed version of " + id, e);
654
                    throw new IllegalStateException("Couldn't get installed version of " + id, e);
661
                }
655
                }
Line 777... Line 771...
777
 
771
 
778
    private SQLElementDirectory getDirectory() {
772
    private SQLElementDirectory getDirectory() {
779
        return getConf().getDirectory();
773
        return getConf().getDirectory();
780
    }
774
    }
781
 
775
 
782
    final File getLocalDirectory() {
776
    public final File getLocalDirectory() {
783
        return new File(this.getConf().getConfDir(getRoot()), "modules");
777
        return new File(this.getConf().getConfDir(getRoot()), "modules");
784
    }
778
    }
785
 
779
 
-
 
780
    public final Set<String> migrateOldTransientDirs() {
-
 
781
        Set<String> res = Collections.emptySet();
-
 
782
        final File[] listFiles = this.getLocalDirectory().listFiles();
-
 
783
        if (listFiles != null) {
-
 
784
            for (final File f : listFiles) {
-
 
785
                if (f.isDirectory()) {
-
 
786
                    res = migrateOldDir(res, f, OLD_BACKUP_DIR_SUFFIX);
-
 
787
                    res = migrateOldDir(res, f, OLD_FAILED_DIR_SUFFIX);
-
 
788
                }
-
 
789
            }
-
 
790
        }
-
 
791
        return res;
-
 
792
    }
-
 
793
 
-
 
794
    private final String getOldID(final File f, final String oldSuffix) {
-
 
795
        if (f.getName().endsWith(oldSuffix)) {
-
 
796
            final String id = f.getName().substring(0, f.getName().length() - oldSuffix.length());
-
 
797
            return ModuleReference.checkID(id, false);
-
 
798
        }
-
 
799
        return null;
-
 
800
    }
-
 
801
 
-
 
802
    private final Set<String> migrateOldDir(Set<String> res, final File f, final String oldSuffix) {
-
 
803
        final String id = getOldID(f, oldSuffix);
-
 
804
        if (id != null) {
-
 
805
            final Path localFailedDir = getLocalFailedDirectory(id);
-
 
806
            try {
-
 
807
                if (Files.exists(localFailedDir)) {
-
 
808
                    // newer already exists, remove old one
-
 
809
                    FileUtils.rm_R(f);
-
 
810
                } else {
-
 
811
                    Files.createDirectories(localFailedDir.getParent());
-
 
812
                    Files.move(f.toPath(), localFailedDir);
-
 
813
                }
-
 
814
            } catch (IOException e) {
-
 
815
                L.log(Level.CONFIG, "Couldn't migrate " + f, e);
-
 
816
                if (res.isEmpty())
-
 
817
                    res = new HashSet<>();
-
 
818
                res.add(id);
-
 
819
            }
-
 
820
        }
-
 
821
        return res;
-
 
822
    }
-
 
823
 
786
    // file specifying which module (and only those, dependencies won't be installed automatically)
824
    // file specifying which module (and only those, dependencies won't be installed automatically)
787
    // to install during the next application launch.
825
    // to install during the next application launch.
788
    private final File getToInstallFile() {
826
    private final File getToInstallFile() {
789
        return new File(getLocalDirectory(), "toInstall");
827
        return new File(getLocalDirectory(), "toInstall");
790
    }
828
    }
791
 
829
 
-
 
830
    private final File getLocalDataSubDir(final File f) {
-
 
831
        // TODO return "Local Data" and a migration method
-
 
832
        return f;
-
 
833
    }
-
 
834
 
-
 
835
    // => create "internal state" and "local data" folders inside getLocalDirectory() (i.e.
-
 
836
    // module.id/local data and not local data/module.id so that we can easily copy to
-
 
837
    // getLocalBackupDirectory() or uninstall a module)
-
 
838
 
-
 
839
    private final Path getInternalStateSubDir(final Path f) {
-
 
840
        // TODO return "Internal State" and a migration method
-
 
841
        return f;
-
 
842
    }
-
 
843
 
792
    protected final File getLocalDirectory(final String id) {
844
    protected final File getLocalDataDirectory(final String id) {
-
 
845
        return getLocalDataSubDir(this.getLocalDirectory(id));
-
 
846
    }
-
 
847
 
-
 
848
    private final Path getInternalStateDirectory(final String id) {
-
 
849
        final File l = this.getLocalDirectory(id);
-
 
850
        return l == null ? null : getInternalStateSubDir(l.toPath());
-
 
851
    }
-
 
852
 
-
 
853
    private final File getLocalDirectory(final String id) {
-
 
854
        if (ModuleReference.checkID(id, false) == null)
-
 
855
            return null;
793
        return new File(this.getLocalDirectory(), id);
856
        return new File(this.getLocalDirectory(), id);
794
    }
857
    }
795
 
858
 
-
 
859
    // contains a copy of local module data during install
-
 
860
    protected final Path getLocalBackupDirectory(final String id) {
796
    // TODO module might remove it since it's in getLocalDirectory()
861
        // will be ignored by getModulesVersionInstalledLocally()
-
 
862
        assert ModuleReference.checkID(BACKUP_DIR, false) == null;
-
 
863
        return this.getLocalDirectory().toPath().resolve(BACKUP_DIR).resolve(id);
-
 
864
    }
-
 
865
 
-
 
866
    // contains local module data of the last failed install
-
 
867
    protected final Path getLocalFailedDirectory(final String id) {
-
 
868
        // will be ignored by getModulesVersionInstalledLocally()
-
 
869
        assert ModuleReference.checkID(FAILED_DIR, false) == null;
-
 
870
        return this.getLocalDirectory().toPath().resolve(FAILED_DIR).resolve(id);
-
 
871
    }
-
 
872
 
797
    private final File getLocalVersionFile(final String id) {
873
    private final File getLocalVersionFile(final String id) {
-
 
874
        final Path dir = this.getInternalStateDirectory(id);
-
 
875
        if (dir == null)
-
 
876
            return null;
798
        return new File(this.getLocalDirectory(id), "version");
877
        return new File(dir.toFile(), "version");
799
    }
878
    }
800
 
879
 
801
    public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
880
    public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
802
        return getDBInstalledModules(id).get(id);
881
        return getDBInstalledModules(id).get(id);
803
    }
882
    }
Line 1020... Line 1099...
1020
            // rename it failed
1099
            // rename it failed
1021
            // 2. copy dir to a backup, pass dir to DBContext, then either remove backup or rename
1100
            // 2. copy dir to a backup, pass dir to DBContext, then either remove backup or rename
1022
            // it to dir
1101
            // it to dir
1023
            // Choice 2 is simpler since the module deals with the same directory in both install()
1102
            // Choice 2 is simpler since the module deals with the same directory in both install()
1024
            // and start()
1103
            // and start()
1025
            final File backupDir;
1104
            final Path backupDir;
1026
            // check if we need a backup
1105
            // check if we need a backup
1027
            if (localDir.exists()) {
1106
            if (localDir.exists()) {
1028
                backupDir = FileUtils.addSuffix(localDir, ".backup");
1107
                backupDir = getLocalBackupDirectory(factory.getID());
1029
                FileUtils.rm_R(backupDir);
1108
                FileUtils.rm_R(backupDir, false);
1030
                FileUtils.copyDirectory(localDir, backupDir);
1109
                Files.createDirectories(backupDir.getParent());
-
 
1110
                FileUtils.copyDirectory(localDir.toPath(), backupDir, false, StandardCopyOption.COPY_ATTRIBUTES);
1031
            } else {
1111
            } else {
1032
                backupDir = null;
1112
                backupDir = null;
1033
                FileUtils.mkdir_p(localDir);
1113
                FileUtils.mkdir_p(localDir);
1034
            }
1114
            }
1035
            assert localDir.exists();
1115
            assert localDir.exists();
1036
            try {
1116
            try {
1037
                SQLUtils.executeAtomic(getDS(), new ConnectionHandlerNoSetup<Object, IOException>() {
1117
                SQLUtils.executeAtomic(getDS(), new ConnectionHandlerNoSetup<Object, IOException>() {
1038
                    @Override
1118
                    @Override
1039
                    public Object handle(SQLDataSource ds) throws SQLException, IOException {
1119
                    public Object handle(SQLDataSource ds) throws SQLException, IOException {
1040
                        final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
1120
                        final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
1041
                        final DBContext ctxt = new DBContext(localDir, localVersion, getRoot(), lastInstalledVersion, alreadyCreatedItems.get0(), alreadyCreatedItems.get1(), getDirectory());
1121
                        final DBContext ctxt = new DBContext(getLocalDataSubDir(localDir), localVersion, getRoot(), lastInstalledVersion, alreadyCreatedItems.get0(), alreadyCreatedItems.get1(),
-
 
1122
                                getDirectory());
1042
                        // install local (i.e. ctxt stores the actions to carry on the DB)
1123
                        // install local (i.e. ctxt stores the actions to carry on the DB)
1043
                        // TODO pass a data source with no rights to modify the data definition (or
1124
                        // TODO pass a data source with no rights to modify the data definition (or
1044
                        // even no rights to modify the data if DB version is up to date)
1125
                        // even no rights to modify the data if DB version is up to date)
1045
                        module.install(ctxt);
1126
                        module.install(ctxt);
1046
                        if (!localDir.exists())
1127
                        if (!localDir.exists())
Line 1054... Line 1135...
1054
            } catch (Exception e) {
1135
            } catch (Exception e) {
1055
                // install did not complete successfully
1136
                // install did not complete successfully
1056
                if (getRoot().getServer().getSQLSystem() == SQLSystem.MYSQL)
1137
                if (getRoot().getServer().getSQLSystem() == SQLSystem.MYSQL)
1057
                    L.warning("MySQL cannot rollback DDL statements");
1138
                    L.warning("MySQL cannot rollback DDL statements");
1058
                // keep failed install files and restore previous files
1139
                // keep failed install files and restore previous files
1059
                final File failed = FileUtils.addSuffix(localDir, ".failed");
1140
                final Path failed = getLocalFailedDirectory(factory.getID());
-
 
1141
                boolean moved = false;
-
 
1142
                try {
1060
                if (failed.exists() && !FileUtils.rmR(failed))
1143
                    FileUtils.rm_R(failed, false);
1061
                    L.warning("Couldn't remove " + failed);
1144
                    Files.createDirectories(failed.getParent());
1062
                if (!localDir.renameTo(failed)) {
1145
                    Files.move(localDir.toPath(), failed);
1063
                    L.warning("Couldn't move " + localDir + " to " + failed);
1146
                    final String errorMsg = "Couldn't install " + module + " :\n" + ExceptionUtils.getStackTrace(e);
-
 
1147
                    // TODO as in getLocalVersionFile(), separate internal state (i.e. version and
-
 
1148
                    // error) from module local data
-
 
1149
                    Files.write(getInternalStateSubDir(failed).resolve("Install error.txt"), errorMsg.getBytes(StandardCharsets.UTF_8));
1064
                } else {
1150
                    moved = true;
1065
                    assert !localDir.exists();
1151
                } catch (Exception e1) {
-
 
1152
                    L.log(Level.WARNING, "Couldn't move " + localDir + " to " + failed, e1);
-
 
1153
                }
1066
                    // restore if needed
1154
                // restore if needed
-
 
1155
                if (moved && backupDir != null) {
-
 
1156
                    assert !localDir.exists();
-
 
1157
                    try {
1067
                    if (backupDir != null && !backupDir.renameTo(localDir))
1158
                        Files.move(backupDir, localDir.toPath());
-
 
1159
                    } catch (Exception e1) {
1068
                        L.warning("Couldn't restore " + backupDir + " to " + localDir);
1160
                        L.log(Level.WARNING, "Couldn't restore " + backupDir + " to " + localDir, e1);
-
 
1161
                    }
1069
                }
1162
                }
1070
                throw e;
1163
                throw e;
1071
            }
1164
            }
1072
            // DB transaction was committed, remove backup files
1165
            // DB transaction was committed, remove backup files
1073
            assert localDir.exists();
1166
            assert localDir.exists();
Line 1081... Line 1174...
1081
    private void registerSQLElements(final AbstractModule module, Map<SQLTable, SQLElement> beforeElements) throws IOException {
1174
    private void registerSQLElements(final AbstractModule module, Map<SQLTable, SQLElement> beforeElements) throws IOException {
1082
        final ModuleReference id = module.getFactory().getReference();
1175
        final ModuleReference id = module.getFactory().getReference();
1083
        synchronized (this.modulesElements) {
1176
        synchronized (this.modulesElements) {
1084
            // perhaps check that no other version of the module has been registered
1177
            // perhaps check that no other version of the module has been registered
1085
            if (!this.modulesElements.containsKey(id)) {
1178
            if (!this.modulesElements.containsKey(id)) {
1086
                final String mdVariant = getMDVariant(module.getFactory());
-
 
1087
                // load now so that it's available to ModuleElement in setupElements()
-
 
1088
                final Set<SQLTable> tablesWithMD = loadTranslations(getConf().getTranslator(), module, mdVariant);
-
 
1089
 
-
 
1090
                final SQLElementDirectory dir = getDirectory();
1179
                final SQLElementDirectory dir = getDirectory();
1091
                module.setupElements(dir);
1180
                module.setupElements(dir);
1092
                final IdentityHashMap<SQLElement, SQLElement> elements = new IdentityHashMap<SQLElement, SQLElement>();
1181
                final IdentityHashMap<SQLElement, SQLElement> elements = new IdentityHashMap<SQLElement, SQLElement>();
1093
                // use IdentitySet so as not to call equals() since it triggers initFF()
1182
                // use IdentitySet so as not to call equals() since it triggers initFF()
1094
                final IdentitySet<SQLElement> beforeElementsSet = new IdentityHashSet<SQLElement>(beforeElements.values());
1183
                final IdentitySet<SQLElement> beforeElementsSet = new IdentityHashSet<SQLElement>(beforeElements.values());
Line 1123... Line 1212...
1123
                            elements.put(elem, null);
1212
                            elements.put(elem, null);
1124
                        }
1213
                        }
1125
                    }
1214
                    }
1126
                }
1215
                }
1127
 
1216
 
-
 
1217
                // Load translations after registering elements since SQLFieldTranslator needs them.
-
 
1218
                // If setupElements() needs translations then perhaps we should store translations
-
 
1219
                // with the element code, without needing the element.
-
 
1220
                final String mdVariant = getMDVariant(module.getFactory());
-
 
1221
                final Set<SQLTable> tablesWithMD = loadTranslations(getConf().getTranslator(), module, mdVariant);
-
 
1222
 
1128
                // insert just loaded labels into the search path
1223
                // insert just loaded labels into the search path
1129
                for (final SQLTable tableWithDoc : tablesWithMD) {
1224
                for (final SQLTable tableWithDoc : tablesWithMD) {
1130
                    final SQLElement sqlElem = this.getDirectory().getElement(tableWithDoc);
1225
                    final SQLElement sqlElem = this.getDirectory().getElement(tableWithDoc);
1131
                    if (sqlElem == null)
1226
                    if (sqlElem == null)
1132
                        throw new IllegalStateException("Missing element for table with metadata : " + tableWithDoc);
1227
                        throw new IllegalStateException("Missing element for table with metadata : " + tableWithDoc);
Line 1544... Line 1639...
1544
        }
1639
        }
1545
 
1640
 
1546
        // call it before stopping/uninstalling
1641
        // call it before stopping/uninstalling
1547
        final boolean exit = this.isExitAllowed() && this.needExit(change);
1642
        final boolean exit = this.isExitAllowed() && this.needExit(change);
1548
 
1643
 
-
 
1644
        final DepSolverGraph graph = change.getGraph();
1549
        final Set<ModuleReference> toRemove = change.getReferencesToRemove();
1645
        final Set<ModuleReference> toRemove = change.getReferencesToRemove();
1550
        final Set<ModuleReference> removed;
1646
        final Set<ModuleReference> removed;
1551
        if (toRemove.size() > 0) {
1647
        if (toRemove.size() > 0) {
1552
            final Set<String> idsToInstall = change.getIDsToInstall();
1648
            final Set<String> idsToInstall = change.getIDsToInstall();
1553
 
1649
 
1554
            // limit the number of requests
-
 
1555
            final Map<String, ModuleVersion> dbVersions = this.getDBInstalledModules();
-
 
1556
            removed = new HashSet<ModuleReference>();
1650
            removed = new HashSet<ModuleReference>();
1557
            for (final ModuleReference ref : toRemove) {
1651
            for (final ModuleReference ref : toRemove) {
1558
                // don't uninstall modules to upgrade but since this loop might uninstall modules
1652
                // don't uninstall modules to upgrade but since this loop might uninstall modules
1559
                // needed by ref, at least stop it like uninstallUnsafe() does
1653
                // needed by ref, at least stop it like uninstallUnsafe() does
1560
                if (idsToInstall.contains(ref.getID()))
1654
                if (idsToInstall.contains(ref.getID()))
1561
                    this.stopModule(ref.getID(), false);
1655
                    this.stopModule(ref.getID(), false);
1562
                else if (this.uninstallUnsafe(ref, !change.forceRemove(), dbVersions))
1656
                else if (this.uninstallUnsafe(ref, !change.forceRemove(), change.getInstallState()))
1563
                    removed.add(ref);
1657
                    removed.add(ref);
1564
            }
1658
            }
1565
        } else {
1659
        } else {
1566
            removed = Collections.emptySet();
1660
            removed = Collections.emptySet();
1567
        }
1661
        }
Line 1579... Line 1673...
1579
            // don't use only getReferencesToInstall() as even if no modules need installing, their
1673
            // don't use only getReferencesToInstall() as even if no modules need installing, their
1580
            // state might need to change (e.g. start)
1674
            // state might need to change (e.g. start)
1581
            if (toInstall.size() > 0 || (targetState.compareTo(ModuleState.INSTALLED) > 0 && change.getUserReferencesToInstall().size() > 0)) {
1675
            if (toInstall.size() > 0 || (targetState.compareTo(ModuleState.INSTALLED) > 0 && change.getUserReferencesToInstall().size() > 0)) {
1582
                // record current time and actions
1676
                // record current time and actions
1583
                final File f = getToInstallFile();
1677
                final File f = getToInstallFile();
1584
                final XMLEncoder xmlEncoder = new XMLEncoder(new FileOutputStream(f));
1678
                try (final XMLEncoder xmlEncoder = new XMLEncoder(new FileOutputStream(f))) {
1585
                try {
-
 
1586
                    xmlEncoder.setExceptionListener(XMLCodecUtils.EXCEPTION_LISTENER);
1679
                    xmlEncoder.setExceptionListener(XMLCodecUtils.EXCEPTION_LISTENER);
1587
                    xmlEncoder.setPersistenceDelegate(ModuleVersion.class, ModuleVersion.PERSIST_DELEGATE);
1680
                    xmlEncoder.setPersistenceDelegate(ModuleVersion.class, ModuleVersion.PERSIST_DELEGATE);
1588
                    xmlEncoder.setPersistenceDelegate(ModuleReference.class, ModuleReference.PERSIST_DELEGATE);
1681
                    xmlEncoder.setPersistenceDelegate(ModuleReference.class, ModuleReference.PERSIST_DELEGATE);
1589
                    xmlEncoder.writeObject(TO_INSTALL_VERSION);
1682
                    xmlEncoder.writeObject(TO_INSTALL_VERSION);
1590
                    xmlEncoder.writeObject(new Date());
1683
                    xmlEncoder.writeObject(new Date());
1591
                    xmlEncoder.writeObject(toInstall);
1684
                    xmlEncoder.writeObject(toInstall);
1592
                    xmlEncoder.writeObject(change.getUserReferencesToInstall());
1685
                    xmlEncoder.writeObject(change.getUserReferencesToInstall());
1593
                    xmlEncoder.writeObject(targetState);
1686
                    xmlEncoder.writeObject(targetState);
1594
                    xmlEncoder.writeObject(startPersistent);
1687
                    xmlEncoder.writeObject(startPersistent);
1595
                    xmlEncoder.close();
-
 
1596
                } catch (Exception e) {
1688
                } catch (Exception e) {
1597
                    // try to delete invalid file before throwing exception
1689
                    // try to delete invalid file before throwing exception
1598
                    try {
-
 
1599
                        xmlEncoder.close();
-
 
1600
                    } catch (Exception e1) {
1690
                    // "any catch or finally block is run after the resources have been closed."
1601
                        e1.printStackTrace();
-
 
1602
                    }
-
 
1603
                    f.delete();
1691
                    f.delete();
1604
                    throw e;
1692
                    throw e;
1605
                }
1693
                }
1606
            }
1694
            }
1607
            return ModulesStateChangeResult.onlyRemoved(removed);
1695
            return new ModulesStateChangeResult(removed, change.getReferencesToInstall(), graph, Collections.emptyMap());
1608
        }
1696
        }
1609
 
1697
 
1610
        // don't use getReferencesToInstall() as even if no modules need installing, their state
1698
        // don't use getReferencesToInstall() as even if no modules need installing, their state
1611
        // might need to change (e.g. start)
1699
        // might need to change (e.g. start)
1612
        if (targetState.compareTo(ModuleState.CREATED) < 0)
1700
        if (targetState.compareTo(ModuleState.CREATED) < 0)
1613
            return ModulesStateChangeResult.onlyRemoved(removed);
1701
            return ModulesStateChangeResult.onlyRemoved(removed);
1614
 
1702
 
1615
        final DepSolverGraph graph = change.getGraph();
-
 
1616
        if (graph == null)
1703
        if (graph == null)
1617
            throw new IllegalArgumentException("target state is " + targetState + " but no graph was provided");
1704
            throw new IllegalArgumentException("target state is " + targetState + " but no graph was provided by " + change);
1618
 
1705
 
1619
        // modules created by this method
1706
        // modules created by this method
1620
        final Map<ModuleReference, AbstractModule> modules = new LinkedHashMap<ModuleReference, AbstractModule>(graph.getFactories().size());
1707
        final Map<ModuleReference, AbstractModule> modules = new LinkedHashMap<ModuleReference, AbstractModule>(graph.getFactories().size());
1621
        // MAYBE try to continue even if some modules couldn't be created
1708
        // MAYBE try to continue even if some modules couldn't be created
1622
        final Set<ModuleReference> cannotCreate = Collections.emptySet();
1709
        final Set<ModuleReference> cannotCreate = Collections.emptySet();
Line 1632... Line 1719...
1632
                for (final Entry<Object, ModuleFactory> e : dependenciesFactory.entrySet()) {
1719
                for (final Entry<Object, ModuleFactory> e : dependenciesFactory.entrySet()) {
1633
                    final AbstractModule module = this.createdModules.get(e.getValue());
1720
                    final AbstractModule module = this.createdModules.get(e.getValue());
1634
                    assert module != null;
1721
                    assert module != null;
1635
                    dependenciesModule.put(e.getKey(), module);
1722
                    dependenciesModule.put(e.getKey(), module);
1636
                }
1723
                }
1637
                final AbstractModule createdModule = useableFactory.createModule(this.getLocalDirectory(id), Collections.unmodifiableMap(dependenciesModule));
1724
                final AbstractModule createdModule = useableFactory.createModule(this.getLocalDataDirectory(id), Collections.unmodifiableMap(dependenciesModule));
1638
                modules.put(useableFactory.getReference(), createdModule);
1725
                modules.put(useableFactory.getReference(), createdModule);
1639
                this.createdModules.put(useableFactory, createdModule);
1726
                this.createdModules.put(useableFactory, createdModule);
1640
 
1727
 
1641
                // update graph
1728
                // update graph
1642
                final boolean added = this.dependencyGraph.addVertex(useableFactory);
1729
                final boolean added = this.dependencyGraph.addVertex(useableFactory);
Line 1755... Line 1842...
1755
            final List<Locale> langs = cntrl.getCandidateLocales(baseName, targetLocale);
1842
            final List<Locale> langs = cntrl.getCandidateLocales(baseName, targetLocale);
1756
            // SQLFieldTranslator overwrite, so we need to load from general to specific
1843
            // SQLFieldTranslator overwrite, so we need to load from general to specific
1757
            final ListIterator<Locale> listIterator = CollectionUtils.getListIterator(langs, true);
1844
            final ListIterator<Locale> listIterator = CollectionUtils.getListIterator(langs, true);
1758
            while (listIterator.hasNext()) {
1845
            while (listIterator.hasNext()) {
1759
                final Locale lang = listIterator.next();
1846
                final Locale lang = listIterator.next();
-
 
1847
                final SQLElementNamesFromXML elemNames = new SQLElementNamesFromXML(lang);
-
 
1848
                final String bundleName = cntrl.toBundleName(baseName, lang);
1760
                final String resourceName = cntrl.toResourceName(cntrl.toBundleName(baseName, lang), "xml");
1849
                final String resourceName = cntrl.toResourceName(bundleName, "xml");
1761
                final InputStream ins = module.getClass().getResourceAsStream(resourceName);
1850
                final InputStream ins = module.getClass().getResourceAsStream(resourceName);
1762
                // do not force to have one mapping for each locale
1851
                // do not force to have one mapping for each locale
1763
                if (ins != null) {
1852
                if (ins != null) {
1764
                    L.config("module " + module.getName() + " loading translation from " + resourceName);
1853
                    L.config("module " + module.getName() + " loading translation from " + resourceName);
1765
                    final Set<SQLTable> loadedTables;
1854
                    final Set<SQLTable> loadedTables;
1766
                    try {
1855
                    try {
1767
                        loadedTables = trns.load(getRoot(), mdVariant, ins).get0();
1856
                        loadedTables = trns.load(getRoot(), mdVariant, ins, elemNames).get0();
1768
                    } finally {
1857
                    } finally {
1769
                        ins.close();
1858
                        ins.close();
1770
                    }
1859
                    }
1771
                    if (loadedTables.size() > 0) {
1860
                    if (loadedTables.size() > 0) {
1772
                        res.addAll(loadedTables);
1861
                        res.addAll(loadedTables);
1773
                        found |= true;
1862
                        found |= true;
1774
                    }
1863
                    }
-
 
1864
 
-
 
1865
                    // As in PropsConfiguration.loadTranslations(), perhaps load the class at
-
 
1866
                    // module.getClass().getPackage().getName() + '.' + bundleName
-
 
1867
                    // to allow more flexibility. Perhaps pass a ModuleSQLFieldTranslator to
-
 
1868
                    // restrict what a module can do (and have SQLElementNamesFromXML, mdVariant).
1775
                }
1869
                }
1776
            }
1870
            }
1777
        }
1871
        }
1778
        return res;
1872
        return res;
1779
    }
1873
    }
Line 1857... Line 1951...
1857
            res.add(f.getReference());
1951
            res.add(f.getReference());
1858
        }
1952
        }
1859
        return res;
1953
        return res;
1860
    }
1954
    }
1861
 
1955
 
-
 
1956
    public final Set<ModuleReference> stopAllModules() {
-
 
1957
        // this method is not synchronized, so don't just return getRunningModules()
-
 
1958
        final Set<ModuleReference> res = new HashSet<>();
-
 
1959
        for (final String id : this.getRunningModules().keySet()) {
-
 
1960
            res.addAll(this.stopModuleRecursively(id));
-
 
1961
        }
-
 
1962
        return res;
-
 
1963
    }
-
 
1964
 
1862
    public synchronized final void stopModuleRecursively(final String id) {
1965
    public synchronized final List<ModuleReference> stopModuleRecursively(final String id) {
1863
        for (final ModuleReference ref : getRunningDependentModulesRecursively(id)) {
1966
        final List<ModuleReference> res = getRunningDependentModulesRecursively(id);
-
 
1967
        for (final ModuleReference ref : res) {
1864
            this.stopModule(ref.getID());
1968
            this.stopModule(ref.getID());
1865
        }
1969
        }
-
 
1970
        return res;
1866
    }
1971
    }
1867
 
1972
 
1868
    public final void stopModule(final String id) {
1973
    public final boolean stopModule(final String id) {
1869
        this.stopModule(id, true);
1974
        return this.stopModule(id, true);
1870
    }
1975
    }
1871
 
1976
 
1872
    // TODO pass ModuleReference instead of ID (need to change this.runningModules)
1977
    // TODO pass ModuleReference instead of ID (need to change this.runningModules)
1873
    public synchronized final void stopModule(final String id, final boolean persistent) {
1978
    public synchronized final boolean stopModule(final String id, final boolean persistent) {
1874
        if (!this.isModuleRunning(id))
1979
        if (!this.isModuleRunning(id))
1875
            return;
1980
            return false;
1876
 
1981
 
1877
        final ModuleFactory f = this.runningModules.get(id).getFactory();
1982
        final ModuleFactory f = this.runningModules.get(id).getFactory();
1878
        if (this.isAdminRequired(f.getReference()) && !currentUserIsAdmin())
1983
        if (this.isAdminRequired(f.getReference()) && !currentUserIsAdmin())
1879
            throw new IllegalStateException("Not allowed to stop a module required by the administrator " + f);
1984
            throw new IllegalStateException("Not allowed to stop a module required by the administrator " + f);
1880
        final Set<DepLink> deps = this.dependencyGraph.incomingEdgesOf(f);
1985
        final Set<DepLink> deps = this.dependencyGraph.incomingEdgesOf(f);
Line 1905... Line 2010...
1905
        // we can't undo what the module has done, so just start from the base menu and re-apply all
2010
        // we can't undo what the module has done, so just start from the base menu and re-apply all
1906
        // modifications
2011
        // modifications
1907
        final MenuAndActions menuAndActions = MenuManager.getInstance().createBaseMenuAndActions();
2012
        final MenuAndActions menuAndActions = MenuManager.getInstance().createBaseMenuAndActions();
1908
        final ArrayList<AbstractModule> modules = new ArrayList<AbstractModule>(this.runningModules.values());
2013
        final ArrayList<AbstractModule> modules = new ArrayList<AbstractModule>(this.runningModules.values());
1909
        SwingThreadUtils.invoke(new Runnable() {
2014
        SwingThreadUtils.invoke(new Runnable() {
-
 
2015
 
1910
            @Override
2016
            @Override
1911
            public void run() {
2017
            public void run() {
1912
                for (final AbstractModule m : modules) {
2018
                for (final AbstractModule m : modules) {
1913
                    setupMenu(m, menuAndActions);
2019
                    setupMenu(m, menuAndActions);
1914
                }
2020
                }
1915
                MenuManager.getInstance().setMenuAndActions(menuAndActions);
2021
                MenuManager.getInstance().setMenuAndActions(menuAndActions);
1916
            }
2022
            }
-
 
2023
 
1917
        });
2024
        });
1918
 
2025
 
1919
        if (persistent)
2026
        if (persistent)
1920
            getRunningIDsPrefs().remove(m.getFactory().getID());
2027
            getRunningIDsPrefs().remove(m.getFactory().getID());
1921
        assert !this.isModuleRunning(id);
2028
        assert !this.isModuleRunning(id);
-
 
2029
        return true;
1922
    }
2030
    }
1923
 
2031
 
1924
    private final void stopModule(final AbstractModule m) {
2032
    private final void stopModule(final AbstractModule m) {
1925
        // this must not attempt to lock this monitor, see uninstallUnsafe()
2033
        // this must not attempt to lock this monitor, see uninstallUnsafe()
1926
        assert SwingUtilities.isEventDispatchThread();
2034
        assert SwingUtilities.isEventDispatchThread();
Line 2129... Line 2237...
2129
 
2237
 
2130
            @Override
2238
            @Override
2131
            public DepSolverGraph getGraph() {
2239
            public DepSolverGraph getGraph() {
2132
                return null;
2240
                return null;
2133
            }
2241
            }
-
 
2242
 
-
 
2243
            @Override
-
 
2244
            public String toString() {
-
 
2245
                return "Uninstall solution for " + this.getReferencesToRemove();
-
 
2246
            }
2134
        };
2247
        };
2135
    }
2248
    }
2136
 
2249
 
2137
    public final void uninstall(final ModuleReference ref) throws Exception {
2250
    public final void uninstall(final ModuleReference ref) throws Exception {
2138
        this.uninstall(ref, false);
2251
        this.uninstall(ref, false);
Line 2144... Line 2257...
2144
 
2257
 
2145
    public synchronized final Set<ModuleReference> uninstall(final ModuleReference id, final boolean recurse, final boolean force) throws Exception {
2258
    public synchronized final Set<ModuleReference> uninstall(final ModuleReference id, final boolean recurse, final boolean force) throws Exception {
2146
        return this.uninstall(Collections.singleton(id), recurse, force);
2259
        return this.uninstall(Collections.singleton(id), recurse, force);
2147
    }
2260
    }
2148
 
2261
 
2149
    // return vers if it matches ref
2262
    // return the version in installed that matches ref
2150
    private final ModuleVersion filter(final ModuleVersion vers, final ModuleReference ref) {
2263
    private final ModuleReference filter(final Set<ModuleReference> installed, final ModuleReference ref) {
-
 
2264
        for (final ModuleReference installedRef : installed) {
2151
        return ref.getVersion() == null || vers != null && vers.equals(ref.getVersion()) ? vers : null;
2265
            if (installedRef.getID().equals(ref.getID()) && (ref.getVersion() == null || installedRef.getVersion().equals(ref.getVersion())))
-
 
2266
                return installedRef;
-
 
2267
        }
-
 
2268
        return null;
2152
    }
2269
    }
2153
 
2270
 
2154
    // unsafe because this method doesn't check dependents
2271
    // unsafe because this method doesn't check dependents
2155
    // dbVersions parameter to avoid requests to the DB
2272
    // dbVersions parameter to avoid requests to the DB
2156
    // return true if the mref was actually uninstalled (i.e. it was installed locally or remotely)
2273
    // return true if the mref was actually uninstalled (i.e. it was installed locally or remotely)
2157
    private boolean uninstallUnsafe(final ModuleReference mref, final boolean requireModule, Map<String, ModuleVersion> dbVersions) throws SQLException, Exception {
2274
    private boolean uninstallUnsafe(final ModuleReference mref, final boolean requireModule, final InstallationState installState) throws SQLException, Exception {
2158
        assert Thread.holdsLock(this);
2275
        assert Thread.holdsLock(this);
2159
        final String id = mref.getID();
2276
        final String id = mref.getID();
2160
        if (dbVersions == null)
-
 
2161
            dbVersions = this.getDBInstalledModules();
-
 
2162
        // versions to uninstall
2277
        // versions to uninstall
2163
        final ModuleVersion localVersion = filter(this.getModuleVersionInstalledLocally(id), mref);
2278
        final ModuleReference localRef = filter(installState.getLocal(), mref);
2164
        final ModuleVersion dbVersion = filter(dbVersions.get(id), mref);
2279
        final ModuleReference dbRef = filter(installState.getRemote(), mref);
-
 
2280
        final ModuleVersion dbVersion = dbRef == null ? null : dbRef.getVersion();
2165
 
2281
 
2166
        // otherwise it will get re-installed the next launch
2282
        // otherwise it will get re-installed the next launch
2167
        getRunningIDsPrefs().remove(id);
2283
        getRunningIDsPrefs().remove(id);
2168
        final Set<ModuleReference> refs = new HashSet<ModuleReference>(2);
2284
        final Set<ModuleReference> refs = new HashSet<ModuleReference>(2);
2169
        if (localVersion != null)
2285
        if (localRef != null)
2170
            refs.add(new ModuleReference(id, localVersion));
2286
            refs.add(localRef);
2171
        if (dbVersion != null)
2287
        if (dbRef != null)
2172
            refs.add(new ModuleReference(id, dbVersion));
2288
            refs.add(dbRef);
2173
        setAdminRequiredModules(refs, false);
2289
        setAdminRequiredModules(refs, false);
2174
 
2290
 
2175
        // only return after having cleared required, so that we don't need to install just to
2291
        // only return after having cleared required, so that we don't need to install just to
2176
        // not require
2292
        // not require
2177
        if (localVersion == null && dbVersion == null)
2293
        if (localRef == null && dbRef == null)
2178
            return false;
2294
            return false;
2179
 
2295
 
2180
        if (dbVersion != null && !currentUserIsAdmin())
2296
        if (dbRef != null && !currentUserIsAdmin())
2181
            throw new IllegalStateException("Not allowed to uninstall " + id + " from the database");
2297
            throw new IllegalStateException("Not allowed to uninstall " + id + " from the database");
2182
 
2298
 
2183
        // DB module
2299
        // DB module
2184
        final AbstractModule module;
2300
        final AbstractModule module;
2185
        if (!this.isModuleRunning(id)) {
2301
        if (!this.isModuleRunning(id)) {
2186
            if (dbVersion == null) {
2302
            if (dbRef == null) {
2187
                assert localVersion != null;
2303
                assert localRef != null;
2188
                // only installed locally
2304
                // only installed locally
2189
                module = null;
2305
                module = null;
2190
            } else {
2306
            } else {
2191
                final SortedMap<ModuleVersion, ModuleFactory> available = this.factories.getVersions(id);
2307
                final SortedMap<ModuleVersion, ModuleFactory> available = this.factories.getVersions(id);
2192
                final ModuleReference ref;
2308
                final ModuleReference ref;
2193
                if (available.containsKey(dbVersion)) {
2309
                if (available.containsKey(dbVersion)) {
2194
                    ref = new ModuleReference(id, dbVersion);
2310
                    ref = dbRef;
2195
                } else {
2311
                } else {
2196
                    // perhaps modules should specify which versions they can uninstall
2312
                    // perhaps modules should specify which versions they can uninstall
2197
                    final SortedMap<ModuleVersion, ModuleFactory> moreRecent = available.headMap(dbVersion);
2313
                    final SortedMap<ModuleVersion, ModuleFactory> moreRecent = available.headMap(dbVersion);
2198
                    if (moreRecent.size() == 0) {
2314
                    if (moreRecent.size() == 0) {
2199
                        ref = null;
2315
                        ref = null;
Line 2206... Line 2322...
2206
                    assert ref.getVersion().compareTo(dbVersion) >= 0;
2322
                    assert ref.getVersion().compareTo(dbVersion) >= 0;
2207
                    final ModuleFactory f = available.get(ref.getVersion());
2323
                    final ModuleFactory f = available.get(ref.getVersion());
2208
                    assert f != null;
2324
                    assert f != null;
2209
                    // only call expensive method if necessary
2325
                    // only call expensive method if necessary
2210
                    if (!this.createdModules.containsKey(f)) {
2326
                    if (!this.createdModules.containsKey(f)) {
2211
                        // don't use the result, instead use this.createdModules since the module
2327
                        // * Don't use the result, instead use this.createdModules since the module
2212
                        // might have been created before
2328
                        // might have been created before.
-
 
2329
                        // * Cannot call directly applyChange(), we need DepSolver to create modules
-
 
2330
                        // that ref depends on, as they might be required by
-
 
2331
                        // AbstractModule.uninstall().
-
 
2332
                        // * Cannot pass NoChoicePredicate.NO_CHANGE as ref won't be created if not
-
 
2333
                        // already installed both locally and remotely. No installation will occur
-
 
2334
                        // since we pass ModuleState.CREATED.
2213
                        this.createModules(Collections.singleton(ref), NoChoicePredicate.NO_CHANGE, ModuleState.CREATED);
2335
                        this.createModules(Collections.singleton(ref), NoChoicePredicate.ONLY_INSTALL, ModuleState.CREATED);
2214
                    }
2336
                    }
2215
                    module = this.createdModules.get(f);
2337
                    module = this.createdModules.get(f);
2216
                } else {
2338
                } else {
2217
                    module = null;
2339
                    module = null;
2218
                }
2340
                }
Line 2226... Line 2348...
2226
                    }
2348
                    }
2227
                    throw new IllegalStateException("Couldn't get module " + id + " : " + reason);
2349
                    throw new IllegalStateException("Couldn't get module " + id + " : " + reason);
2228
                }
2350
                }
2229
            }
2351
            }
2230
        } else {
2352
        } else {
-
 
2353
            final ModuleVersion localVersion = localRef.getVersion();
2231
            if (!localVersion.equals(dbVersion))
2354
            if (!localVersion.equals(dbVersion))
2232
                L.warning("Someone else has changed the database version while we were running :" + localVersion + " != " + dbVersion);
2355
                L.warning("Someone else has changed the database version while we were running :" + localVersion + " != " + dbVersion);
2233
            module = this.runningModules.get(id);
2356
            module = this.runningModules.get(id);
2234
            assert localVersion.equals(module.getFactory().getVersion());
2357
            assert localVersion.equals(module.getFactory().getVersion());
2235
            this.stopModule(id, false);
2358
            this.stopModule(id, false);
Line 2237... Line 2360...
2237
            // ATTN we hold this monitor, so stop() should never try to acquire it in the EDT
2360
            // ATTN we hold this monitor, so stop() should never try to acquire it in the EDT
2238
            if (!SwingUtilities.isEventDispatchThread()) {
2361
            if (!SwingUtilities.isEventDispatchThread()) {
2239
                SwingUtilities.invokeAndWait(EMPTY_RUNNABLE);
2362
                SwingUtilities.invokeAndWait(EMPTY_RUNNABLE);
2240
            }
2363
            }
2241
        }
2364
        }
2242
        assert (module == null) == (!requireModule || dbVersion == null);
2365
        assert (module == null) == (!requireModule || dbRef == null);
2243
 
2366
 
2244
        SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
2367
        SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
2245
            @Override
2368
            @Override
2246
            public Object create() throws SQLException {
2369
            public Object create() throws SQLException {
2247
                final DBRoot root = getRoot();
2370
                final DBRoot root = getRoot();
2248
                if (module != null) {
2371
                if (module != null) {
2249
                    module.uninstall(root);
2372
                    module.uninstall(root);
2250
                    unregisterSQLElements(module);
2373
                    unregisterSQLElements(module);
2251
                }
2374
                }
2252
                if (localVersion != null)
2375
                if (localRef != null)
2253
                    setModuleInstalledLocally(new ModuleReference(id, localVersion), false);
2376
                    setModuleInstalledLocally(localRef, false);
2254
 
2377
 
2255
                // uninstall from DB
2378
                // uninstall from DB
2256
                if (dbVersion != null) {
2379
                if (dbRef != null) {
2257
                    final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
2380
                    final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
2258
                    final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
2381
                    final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
2259
                    final Set<String> tableNames = createdItems.get0();
2382
                    final Set<String> tableNames = createdItems.get0();
2260
                    for (final SQLName field : createdItems.get1()) {
2383
                    for (final SQLName field : createdItems.get1()) {
2261
                        final SQLField f = root.getDesc(field, SQLField.class);
2384
                        final SQLField f = root.getDesc(field, SQLField.class);
Line 2274... Line 2397...
2274
                            root.getDBSystemRoot().getDataSource().execute(s);
2397
                            root.getDBSystemRoot().getDataSource().execute(s);
2275
                        root.getSchema().updateVersion();
2398
                        root.getSchema().updateVersion();
2276
                        root.refetch();
2399
                        root.refetch();
2277
                    }
2400
                    }
2278
 
2401
 
2279
                    removeModuleFields(new ModuleReference(id, dbVersion));
2402
                    removeModuleFields(dbRef);
2280
                }
2403
                }
2281
                return null;
2404
                return null;
2282
            }
2405
            }
2283
        });
2406
        });
2284
        return true;
2407
        return true;