18 |
ilm |
1 |
/*
|
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
|
3 |
*
|
182 |
ilm |
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
18 |
ilm |
5 |
*
|
|
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
|
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
|
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
|
|
9 |
* language governing permissions and limitations under the License.
|
|
|
10 |
*
|
|
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
|
|
12 |
*/
|
|
|
13 |
|
|
|
14 |
package org.openconcerto.erp.modules;
|
|
|
15 |
|
67 |
ilm |
16 |
import org.openconcerto.erp.config.Log;
|
18 |
ilm |
17 |
import org.openconcerto.erp.config.MainFrame;
|
73 |
ilm |
18 |
import org.openconcerto.erp.config.MenuAndActions;
|
|
|
19 |
import org.openconcerto.erp.config.MenuManager;
|
80 |
ilm |
20 |
import org.openconcerto.erp.modules.DepSolverResult.Factory;
|
|
|
21 |
import org.openconcerto.erp.modules.ModuleTableModel.ModuleRow;
|
18 |
ilm |
22 |
import org.openconcerto.sql.Configuration;
|
19 |
ilm |
23 |
import org.openconcerto.sql.element.SQLElement;
|
|
|
24 |
import org.openconcerto.sql.element.SQLElementDirectory;
|
156 |
ilm |
25 |
import org.openconcerto.sql.element.SQLElementNamesFromXML;
|
80 |
ilm |
26 |
import org.openconcerto.sql.model.AliasedTable;
|
25 |
ilm |
27 |
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
|
18 |
ilm |
28 |
import org.openconcerto.sql.model.DBFileCache;
|
|
|
29 |
import org.openconcerto.sql.model.DBItemFileCache;
|
|
|
30 |
import org.openconcerto.sql.model.DBRoot;
|
25 |
ilm |
31 |
import org.openconcerto.sql.model.SQLDataSource;
|
19 |
ilm |
32 |
import org.openconcerto.sql.model.SQLField;
|
|
|
33 |
import org.openconcerto.sql.model.SQLName;
|
80 |
ilm |
34 |
import org.openconcerto.sql.model.SQLRow;
|
|
|
35 |
import org.openconcerto.sql.model.SQLRowListRSH;
|
|
|
36 |
import org.openconcerto.sql.model.SQLRowValues;
|
|
|
37 |
import org.openconcerto.sql.model.SQLSelect;
|
|
|
38 |
import org.openconcerto.sql.model.SQLSyntax;
|
25 |
ilm |
39 |
import org.openconcerto.sql.model.SQLSystem;
|
18 |
ilm |
40 |
import org.openconcerto.sql.model.SQLTable;
|
80 |
ilm |
41 |
import org.openconcerto.sql.model.TableRef;
|
|
|
42 |
import org.openconcerto.sql.model.Where;
|
18 |
ilm |
43 |
import org.openconcerto.sql.model.graph.DirectedEdge;
|
80 |
ilm |
44 |
import org.openconcerto.sql.model.graph.Link.Rule;
|
27 |
ilm |
45 |
import org.openconcerto.sql.preferences.SQLPreferences;
|
80 |
ilm |
46 |
import org.openconcerto.sql.request.SQLFieldTranslator;
|
|
|
47 |
import org.openconcerto.sql.users.rights.UserRightsManager;
|
19 |
ilm |
48 |
import org.openconcerto.sql.utils.AlterTable;
|
|
|
49 |
import org.openconcerto.sql.utils.ChangeTable;
|
80 |
ilm |
50 |
import org.openconcerto.sql.utils.ChangeTable.ForeignColSpec;
|
19 |
ilm |
51 |
import org.openconcerto.sql.utils.DropTable;
|
80 |
ilm |
52 |
import org.openconcerto.sql.utils.SQLCreateTable;
|
18 |
ilm |
53 |
import org.openconcerto.sql.utils.SQLUtils;
|
|
|
54 |
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
|
83 |
ilm |
55 |
import org.openconcerto.sql.view.list.IListeAction;
|
73 |
ilm |
56 |
import org.openconcerto.ui.SwingThreadUtils;
|
182 |
ilm |
57 |
import org.openconcerto.utils.BaseDirs;
|
80 |
ilm |
58 |
import org.openconcerto.utils.CollectionMap2.Mode;
|
19 |
ilm |
59 |
import org.openconcerto.utils.CollectionUtils;
|
18 |
ilm |
60 |
import org.openconcerto.utils.ExceptionHandler;
|
156 |
ilm |
61 |
import org.openconcerto.utils.ExceptionUtils;
|
25 |
ilm |
62 |
import org.openconcerto.utils.FileUtils;
|
80 |
ilm |
63 |
import org.openconcerto.utils.SetMap;
|
19 |
ilm |
64 |
import org.openconcerto.utils.StringUtils;
|
61 |
ilm |
65 |
import org.openconcerto.utils.ThreadFactory;
|
18 |
ilm |
66 |
import org.openconcerto.utils.Tuple2;
|
80 |
ilm |
67 |
import org.openconcerto.utils.Tuple3;
|
18 |
ilm |
68 |
import org.openconcerto.utils.cc.IClosure;
|
19 |
ilm |
69 |
import org.openconcerto.utils.cc.IdentityHashSet;
|
25 |
ilm |
70 |
import org.openconcerto.utils.cc.IdentitySet;
|
73 |
ilm |
71 |
import org.openconcerto.utils.i18n.TranslationManager;
|
80 |
ilm |
72 |
import org.openconcerto.xml.XMLCodecUtils;
|
18 |
ilm |
73 |
|
80 |
ilm |
74 |
import java.beans.XMLDecoder;
|
|
|
75 |
import java.beans.XMLEncoder;
|
18 |
ilm |
76 |
import java.io.File;
|
|
|
77 |
import java.io.FileFilter;
|
80 |
ilm |
78 |
import java.io.FileInputStream;
|
|
|
79 |
import java.io.FileOutputStream;
|
18 |
ilm |
80 |
import java.io.IOException;
|
19 |
ilm |
81 |
import java.io.InputStream;
|
156 |
ilm |
82 |
import java.nio.charset.StandardCharsets;
|
|
|
83 |
import java.nio.file.Files;
|
|
|
84 |
import java.nio.file.Path;
|
|
|
85 |
import java.nio.file.StandardCopyOption;
|
18 |
ilm |
86 |
import java.sql.SQLException;
|
|
|
87 |
import java.util.ArrayList;
|
|
|
88 |
import java.util.Arrays;
|
|
|
89 |
import java.util.Collection;
|
|
|
90 |
import java.util.Collections;
|
80 |
ilm |
91 |
import java.util.Date;
|
18 |
ilm |
92 |
import java.util.HashMap;
|
|
|
93 |
import java.util.HashSet;
|
80 |
ilm |
94 |
import java.util.IdentityHashMap;
|
|
|
95 |
import java.util.Iterator;
|
18 |
ilm |
96 |
import java.util.LinkedHashMap;
|
19 |
ilm |
97 |
import java.util.LinkedHashSet;
|
80 |
ilm |
98 |
import java.util.LinkedList;
|
18 |
ilm |
99 |
import java.util.List;
|
80 |
ilm |
100 |
import java.util.ListIterator;
|
|
|
101 |
import java.util.Locale;
|
18 |
ilm |
102 |
import java.util.Map;
|
25 |
ilm |
103 |
import java.util.Map.Entry;
|
80 |
ilm |
104 |
import java.util.ResourceBundle.Control;
|
20 |
ilm |
105 |
import java.util.Set;
|
80 |
ilm |
106 |
import java.util.SortedMap;
|
|
|
107 |
import java.util.TreeSet;
|
73 |
ilm |
108 |
import java.util.concurrent.Callable;
|
61 |
ilm |
109 |
import java.util.concurrent.Executor;
|
67 |
ilm |
110 |
import java.util.concurrent.ExecutorService;
|
73 |
ilm |
111 |
import java.util.concurrent.FutureTask;
|
61 |
ilm |
112 |
import java.util.concurrent.LinkedBlockingQueue;
|
|
|
113 |
import java.util.concurrent.ThreadPoolExecutor;
|
|
|
114 |
import java.util.concurrent.TimeUnit;
|
80 |
ilm |
115 |
import java.util.logging.Level;
|
19 |
ilm |
116 |
import java.util.logging.Logger;
|
80 |
ilm |
117 |
import java.util.prefs.BackingStoreException;
|
18 |
ilm |
118 |
import java.util.prefs.Preferences;
|
|
|
119 |
|
|
|
120 |
import javax.swing.SwingUtilities;
|
|
|
121 |
|
61 |
ilm |
122 |
import net.jcip.annotations.GuardedBy;
|
|
|
123 |
import net.jcip.annotations.ThreadSafe;
|
|
|
124 |
|
18 |
ilm |
125 |
/**
|
|
|
126 |
* Hold the list of known modules and their status.
|
|
|
127 |
*
|
|
|
128 |
* @author Sylvain CUAZ
|
|
|
129 |
*/
|
61 |
ilm |
130 |
@ThreadSafe
|
18 |
ilm |
131 |
public class ModuleManager {
|
|
|
132 |
|
67 |
ilm |
133 |
/**
|
80 |
ilm |
134 |
* The right to install/uninstall modules in the database (everyone can install locally).
|
|
|
135 |
*/
|
|
|
136 |
static public final String MODULE_DB_RIGHT = "moduleDBAdmin";
|
67 |
ilm |
137 |
|
80 |
ilm |
138 |
static final Logger L = Logger.getLogger(ModuleManager.class.getPackage().getName());
|
67 |
ilm |
139 |
@GuardedBy("ModuleManager.class")
|
|
|
140 |
private static ExecutorService exec = null;
|
19 |
ilm |
141 |
|
67 |
ilm |
142 |
private static synchronized final Executor getExec() {
|
|
|
143 |
if (exec == null)
|
156 |
ilm |
144 |
exec = new ThreadPoolExecutor(0, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory(ModuleManager.class.getSimpleName()
|
|
|
145 |
// not daemon since install() is not atomic
|
|
|
146 |
+ " executor thread ", false));
|
67 |
ilm |
147 |
return exec;
|
|
|
148 |
}
|
|
|
149 |
|
80 |
ilm |
150 |
static final Runnable EMPTY_RUNNABLE = new Runnable() {
|
|
|
151 |
@Override
|
|
|
152 |
public void run() {
|
|
|
153 |
}
|
|
|
154 |
};
|
67 |
ilm |
155 |
|
80 |
ilm |
156 |
// public needed for XMLEncoder
|
|
|
157 |
static public enum ModuleState {
|
|
|
158 |
NOT_CREATED, CREATED, INSTALLED, REGISTERED, STARTED
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
static enum ModuleAction {
|
|
|
162 |
INSTALL, START, STOP, UNINSTALL
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
private static final long MIN_VERSION = ModuleVersion.MIN.getMerged();
|
|
|
166 |
private static final String MODULE_COLNAME = "MODULE_NAME";
|
|
|
167 |
private static final String MODULE_VERSION_COLNAME = "MODULE_VERSION";
|
|
|
168 |
private static final String TABLE_COLNAME = "TABLE";
|
|
|
169 |
private static final String FIELD_COLNAME = "FIELD";
|
|
|
170 |
private static final String ISKEY_COLNAME = "KEY";
|
|
|
171 |
// Don't use String literals for the synchronized blocks
|
|
|
172 |
private static final String FWK_MODULE_TABLENAME = new String("FWK_MODULE_METADATA");
|
|
|
173 |
private static final String FWK_MODULE_DEP_TABLENAME = new String("FWK_MODULE_DEP");
|
|
|
174 |
private static final String NEEDING_MODULE_COLNAME = "ID_MODULE";
|
|
|
175 |
private static final String NEEDED_MODULE_COLNAME = "ID_MODULE_NEEDED";
|
61 |
ilm |
176 |
private static final String fileMutex = new String("modules");
|
80 |
ilm |
177 |
|
|
|
178 |
private static final Integer TO_INSTALL_VERSION = 1;
|
|
|
179 |
|
156 |
ilm |
180 |
private static final String OLD_BACKUP_DIR_SUFFIX = ".backup";
|
|
|
181 |
private static final String OLD_FAILED_DIR_SUFFIX = ".failed";
|
|
|
182 |
private static final String BACKUP_DIR = "Install backup";
|
|
|
183 |
private static final String FAILED_DIR = "Install failed";
|
18 |
ilm |
184 |
|
80 |
ilm |
185 |
// return true if the MainFrame is not displayable (or if there's none)
|
|
|
186 |
static private boolean noDisplayableFrame() {
|
|
|
187 |
final MainFrame mf = MainFrame.getInstance();
|
|
|
188 |
if (mf == null)
|
|
|
189 |
return true;
|
|
|
190 |
final FutureTask<Boolean> f = new FutureTask<Boolean>(new Callable<Boolean>() {
|
|
|
191 |
@Override
|
|
|
192 |
public Boolean call() throws Exception {
|
|
|
193 |
return !mf.isDisplayable();
|
|
|
194 |
}
|
|
|
195 |
});
|
|
|
196 |
SwingThreadUtils.invoke(f);
|
|
|
197 |
try {
|
|
|
198 |
return f.get();
|
|
|
199 |
} catch (Exception e) {
|
|
|
200 |
Log.get().log(Level.WARNING, "Couldn't determine MainFrame displayability", e);
|
|
|
201 |
return true;
|
|
|
202 |
}
|
|
|
203 |
}
|
|
|
204 |
|
67 |
ilm |
205 |
public static synchronized void tearDown() {
|
|
|
206 |
if (exec != null) {
|
|
|
207 |
exec.shutdown();
|
|
|
208 |
exec = null;
|
|
|
209 |
}
|
|
|
210 |
}
|
|
|
211 |
|
80 |
ilm |
212 |
public static String getMDVariant(ModuleFactory f) {
|
|
|
213 |
return getMDVariant(f.getReference());
|
57 |
ilm |
214 |
}
|
|
|
215 |
|
80 |
ilm |
216 |
public static String getMDVariant(ModuleReference ref) {
|
|
|
217 |
return ref.getID();
|
|
|
218 |
}
|
|
|
219 |
|
|
|
220 |
static final Set<ModuleReference> versionsMapToSet(final Map<String, ModuleVersion> versions) {
|
|
|
221 |
final Set<ModuleReference> res = new HashSet<ModuleReference>(versions.size());
|
|
|
222 |
for (final Entry<String, ModuleVersion> e : versions.entrySet())
|
|
|
223 |
res.add(new ModuleReference(e.getKey(), e.getValue()));
|
|
|
224 |
return res;
|
|
|
225 |
}
|
|
|
226 |
|
|
|
227 |
// final (thus safely published) and thread-safe
|
|
|
228 |
private final FactoriesByID factories;
|
182 |
ilm |
229 |
@GuardedBy("this")
|
|
|
230 |
private List<File> folders = Collections.emptyList();
|
80 |
ilm |
231 |
// to avoid starting twice the same module
|
61 |
ilm |
232 |
// we synchronize the whole install/start and stop/uninstall
|
|
|
233 |
@GuardedBy("this")
|
18 |
ilm |
234 |
private final Map<String, AbstractModule> runningModules;
|
61 |
ilm |
235 |
// in fact it is also already guarded by "this"
|
|
|
236 |
@GuardedBy("modulesElements")
|
80 |
ilm |
237 |
private final Map<ModuleReference, IdentityHashMap<SQLElement, SQLElement>> modulesElements;
|
|
|
238 |
@GuardedBy("this")
|
|
|
239 |
private boolean inited;
|
61 |
ilm |
240 |
// only in EDT
|
19 |
ilm |
241 |
private final Map<String, ComponentsContext> modulesComponents;
|
80 |
ilm |
242 |
// graph of created modules, ATTN since we have no way to "unload" a module we only add and
|
|
|
243 |
// never remove from it
|
61 |
ilm |
244 |
@GuardedBy("this")
|
80 |
ilm |
245 |
private final DependencyGraph dependencyGraph;
|
|
|
246 |
@GuardedBy("this")
|
|
|
247 |
private final Map<ModuleFactory, AbstractModule> createdModules;
|
18 |
ilm |
248 |
|
80 |
ilm |
249 |
// Another mutex so we can query root or conf without having to wait for modules to
|
|
|
250 |
// install/uninstall, or alternatively so that start() & stop() executed in the EDT don't need
|
|
|
251 |
// this monitor (see uninstallUnsafe()). This lock is a leaf lock, it mustn't call code that
|
|
|
252 |
// might need another lock.
|
|
|
253 |
private final Object varLock = new String("varLock");
|
|
|
254 |
@GuardedBy("varLock")
|
25 |
ilm |
255 |
private DBRoot root;
|
61 |
ilm |
256 |
@GuardedBy("this")
|
80 |
ilm |
257 |
private SQLPreferences dbPrefs;
|
|
|
258 |
@GuardedBy("varLock")
|
25 |
ilm |
259 |
private Configuration conf;
|
80 |
ilm |
260 |
@GuardedBy("varLock")
|
|
|
261 |
private boolean exitAllowed;
|
25 |
ilm |
262 |
|
18 |
ilm |
263 |
public ModuleManager() {
|
80 |
ilm |
264 |
this.factories = new FactoriesByID();
|
|
|
265 |
// stopModule() needs order to reset menu
|
|
|
266 |
this.runningModules = new LinkedHashMap<String, AbstractModule>();
|
|
|
267 |
this.dependencyGraph = new DependencyGraph();
|
|
|
268 |
this.createdModules = new LinkedHashMap<ModuleFactory, AbstractModule>();
|
|
|
269 |
this.modulesElements = new HashMap<ModuleReference, IdentityHashMap<SQLElement, SQLElement>>();
|
|
|
270 |
this.inited = false;
|
19 |
ilm |
271 |
this.modulesComponents = new HashMap<String, ComponentsContext>();
|
25 |
ilm |
272 |
|
|
|
273 |
this.root = null;
|
80 |
ilm |
274 |
this.dbPrefs = null;
|
25 |
ilm |
275 |
this.conf = null;
|
80 |
ilm |
276 |
this.exitAllowed = true;
|
|
|
277 |
}
|
67 |
ilm |
278 |
|
80 |
ilm |
279 |
/**
|
|
|
280 |
* Whether the current user can manage modules.
|
|
|
281 |
*
|
|
|
282 |
* @return <code>true</code> if the current user can manage modules.
|
|
|
283 |
*/
|
|
|
284 |
public final boolean currentUserIsAdmin() {
|
|
|
285 |
return UserRightsManager.getCurrentUserRights().haveRight(MODULE_DB_RIGHT);
|
18 |
ilm |
286 |
}
|
|
|
287 |
|
80 |
ilm |
288 |
// AdminRequiredModules means installed & started
|
|
|
289 |
// possible AdminForbiddenModules means neither installed nor started
|
|
|
290 |
public final boolean canCurrentUser(final ModuleAction action, final ModuleRow m) {
|
|
|
291 |
if (currentUserIsAdmin())
|
|
|
292 |
return true;
|
|
|
293 |
|
|
|
294 |
if (action == ModuleAction.INSTALL || action == ModuleAction.UNINSTALL)
|
|
|
295 |
return canCurrentUserInstall(action, m.isInstalledRemotely());
|
|
|
296 |
else if (action == ModuleAction.START)
|
|
|
297 |
return true;
|
|
|
298 |
else if (action == ModuleAction.STOP)
|
|
|
299 |
return !m.isAdminRequired();
|
|
|
300 |
else
|
|
|
301 |
throw new IllegalArgumentException("Unknown action " + action);
|
67 |
ilm |
302 |
}
|
|
|
303 |
|
80 |
ilm |
304 |
final boolean canCurrentUserInstall(final ModuleAction action, final ModuleReference ref, final InstallationState state) {
|
|
|
305 |
return this.canCurrentUserInstall(action, state.getRemote().contains(ref));
|
|
|
306 |
}
|
|
|
307 |
|
|
|
308 |
private final boolean canCurrentUserInstall(final ModuleAction action, final boolean installedRemotely) {
|
|
|
309 |
if (currentUserIsAdmin())
|
|
|
310 |
return true;
|
|
|
311 |
|
|
|
312 |
if (action == ModuleAction.INSTALL)
|
|
|
313 |
return installedRemotely;
|
|
|
314 |
else if (action == ModuleAction.UNINSTALL)
|
|
|
315 |
return !installedRemotely;
|
|
|
316 |
else
|
|
|
317 |
throw new IllegalArgumentException("Illegal action " + action);
|
|
|
318 |
}
|
|
|
319 |
|
18 |
ilm |
320 |
// *** factories (thread-safe)
|
|
|
321 |
|
182 |
ilm |
322 |
public final void setFolders(final File... dirs) {
|
|
|
323 |
this.setFolders(Arrays.asList(dirs));
|
|
|
324 |
}
|
|
|
325 |
|
|
|
326 |
// The system directories should be before the user directories.
|
|
|
327 |
// This order is important for getFolderToWrite() and allow the user modules to replace the
|
|
|
328 |
// system ones.
|
|
|
329 |
public final void setFolders(final List<File> dirs) {
|
|
|
330 |
final List<File> absolutes = new ArrayList<>(dirs.size());
|
|
|
331 |
for (final File dir : dirs) {
|
|
|
332 |
final File abs = dir.getAbsoluteFile();
|
|
|
333 |
this.addFactories(abs);
|
|
|
334 |
absolutes.add(abs);
|
|
|
335 |
}
|
|
|
336 |
synchronized (this) {
|
|
|
337 |
this.folders = Collections.unmodifiableList(absolutes);
|
|
|
338 |
}
|
|
|
339 |
}
|
|
|
340 |
|
|
|
341 |
public synchronized final List<File> getFolders() {
|
|
|
342 |
return this.folders;
|
|
|
343 |
}
|
|
|
344 |
|
|
|
345 |
public static final File getFolderToWrite(final List<File> dirs) {
|
|
|
346 |
// Try to install in system directory first, then fall back to user directory.
|
|
|
347 |
for (final File dir : dirs) {
|
|
|
348 |
try {
|
|
|
349 |
return BaseDirs.getFolderToWrite(dir);
|
|
|
350 |
} catch (IOException e) {
|
|
|
351 |
// try next one
|
|
|
352 |
}
|
|
|
353 |
}
|
|
|
354 |
return null;
|
|
|
355 |
}
|
|
|
356 |
|
18 |
ilm |
357 |
public final int addFactories(final File dir) {
|
19 |
ilm |
358 |
if (!dir.exists()) {
|
25 |
ilm |
359 |
L.warning("Module factory directory not found: " + dir.getAbsolutePath());
|
19 |
ilm |
360 |
return 0;
|
|
|
361 |
}
|
18 |
ilm |
362 |
final File[] jars = dir.listFiles(new FileFilter() {
|
|
|
363 |
@Override
|
|
|
364 |
public boolean accept(File f) {
|
|
|
365 |
return f.getName().endsWith(".jar");
|
|
|
366 |
}
|
|
|
367 |
});
|
|
|
368 |
int i = 0;
|
19 |
ilm |
369 |
if (jars != null) {
|
|
|
370 |
for (final File jar : jars) {
|
|
|
371 |
try {
|
|
|
372 |
this.addFactory(new JarModuleFactory(jar));
|
|
|
373 |
i++;
|
|
|
374 |
} catch (Exception e) {
|
25 |
ilm |
375 |
L.warning("Couldn't add " + jar);
|
19 |
ilm |
376 |
e.printStackTrace();
|
|
|
377 |
}
|
18 |
ilm |
378 |
}
|
|
|
379 |
}
|
|
|
380 |
return i;
|
|
|
381 |
}
|
|
|
382 |
|
|
|
383 |
public final ModuleFactory addFactoryFromPackage(File jar) throws IOException {
|
|
|
384 |
final ModuleFactory f = new JarModuleFactory(jar);
|
|
|
385 |
this.addFactory(f);
|
|
|
386 |
return f;
|
|
|
387 |
}
|
|
|
388 |
|
|
|
389 |
/**
|
|
|
390 |
* Adds a factory.
|
|
|
391 |
*
|
|
|
392 |
* @param f the factory to add.
|
|
|
393 |
* @return the ID of the factory.
|
|
|
394 |
*/
|
|
|
395 |
public final String addFactory(ModuleFactory f) {
|
80 |
ilm |
396 |
final ModuleFactory prev = this.factories.add(f);
|
|
|
397 |
if (prev != null)
|
|
|
398 |
L.info("Changing the factory for " + f.getReference() + "\nfrom\t" + prev + "\nto\t" + f);
|
|
|
399 |
return f.getID();
|
18 |
ilm |
400 |
}
|
|
|
401 |
|
80 |
ilm |
402 |
public final void addFactories(Collection<ModuleFactory> factories) {
|
|
|
403 |
for (final ModuleFactory f : factories)
|
|
|
404 |
this.addFactory(f);
|
|
|
405 |
}
|
|
|
406 |
|
18 |
ilm |
407 |
public final String addFactoryAndStart(final ModuleFactory f, final boolean persistent) {
|
|
|
408 |
return this.addFactory(f, true, persistent);
|
|
|
409 |
}
|
|
|
410 |
|
|
|
411 |
private final String addFactory(final ModuleFactory f, final boolean start, final boolean persistent) {
|
80 |
ilm |
412 |
this.addFactory(f);
|
|
|
413 |
if (start) {
|
|
|
414 |
L.config("addFactory() invoked start " + (persistent ? "" : "not") + " persistent for " + f);
|
18 |
ilm |
415 |
this.invoke(new IClosure<ModuleManager>() {
|
|
|
416 |
@Override
|
|
|
417 |
public void executeChecked(ModuleManager input) {
|
|
|
418 |
try {
|
149 |
ilm |
419 |
if (!startModule(f.getReference(), persistent))
|
|
|
420 |
throw new IllegalStateException("Couldn't be started");
|
|
|
421 |
} catch (Throwable e) {
|
18 |
ilm |
422 |
ExceptionHandler.handle(MainFrame.getInstance(), "Unable to start " + f, e);
|
|
|
423 |
}
|
|
|
424 |
}
|
|
|
425 |
});
|
80 |
ilm |
426 |
}
|
18 |
ilm |
427 |
return f.getID();
|
|
|
428 |
}
|
|
|
429 |
|
80 |
ilm |
430 |
public final Map<String, SortedMap<ModuleVersion, ModuleFactory>> getFactories() {
|
|
|
431 |
return this.factories.getMap();
|
18 |
ilm |
432 |
}
|
|
|
433 |
|
80 |
ilm |
434 |
public final FactoriesByID copyFactories() {
|
|
|
435 |
return new FactoriesByID(this.factories);
|
19 |
ilm |
436 |
}
|
|
|
437 |
|
18 |
ilm |
438 |
public final void removeFactory(String id) {
|
80 |
ilm |
439 |
this.factories.remove(id);
|
18 |
ilm |
440 |
}
|
|
|
441 |
|
61 |
ilm |
442 |
// *** modules (thread-safe)
|
18 |
ilm |
443 |
|
|
|
444 |
/**
|
25 |
ilm |
445 |
* Call the passed closure at a time when modules can be started. In particular this manager has
|
|
|
446 |
* been set up and the {@link MainFrame#getInstance() main frame} has been created.
|
|
|
447 |
*
|
|
|
448 |
* @param c the closure to execute.
|
18 |
ilm |
449 |
*/
|
|
|
450 |
public void invoke(final IClosure<ModuleManager> c) {
|
|
|
451 |
MainFrame.invoke(new Runnable() {
|
|
|
452 |
@Override
|
|
|
453 |
public void run() {
|
67 |
ilm |
454 |
getExec().execute(new Runnable() {
|
61 |
ilm |
455 |
@Override
|
|
|
456 |
public void run() {
|
|
|
457 |
c.executeChecked(ModuleManager.this);
|
|
|
458 |
}
|
|
|
459 |
});
|
18 |
ilm |
460 |
}
|
|
|
461 |
});
|
|
|
462 |
}
|
|
|
463 |
|
19 |
ilm |
464 |
/**
|
61 |
ilm |
465 |
* Allow to access certain methods without a full {@link #setup(DBRoot, Configuration)}. If
|
|
|
466 |
* setup() is subsequently called it must be passed the same root instance.
|
|
|
467 |
*
|
|
|
468 |
* @param root the root.
|
|
|
469 |
* @throws IllegalStateException if already set.
|
|
|
470 |
* @see #getDBInstalledModules()
|
|
|
471 |
* @see #getCreatedItems(String)
|
|
|
472 |
*/
|
80 |
ilm |
473 |
public final void setRoot(final DBRoot root) {
|
|
|
474 |
synchronized (this.varLock) {
|
|
|
475 |
if (this.root != root) {
|
|
|
476 |
if (this.root != null)
|
|
|
477 |
throw new IllegalStateException("Root already set");
|
|
|
478 |
this.root = root;
|
|
|
479 |
}
|
61 |
ilm |
480 |
}
|
|
|
481 |
}
|
|
|
482 |
|
80 |
ilm |
483 |
public final boolean isSetup() {
|
|
|
484 |
synchronized (this.varLock) {
|
|
|
485 |
return this.getRoot() != null && this.getConf() != null;
|
|
|
486 |
}
|
61 |
ilm |
487 |
}
|
|
|
488 |
|
|
|
489 |
/**
|
80 |
ilm |
490 |
* Set up the module manager.
|
19 |
ilm |
491 |
*
|
25 |
ilm |
492 |
* @param root the root where the modules install.
|
|
|
493 |
* @param conf the configuration the modules change.
|
80 |
ilm |
494 |
* @throws IllegalStateException if already {@link #isSetup() set up}.
|
19 |
ilm |
495 |
*/
|
80 |
ilm |
496 |
public final void setup(final DBRoot root, final Configuration conf) throws IllegalStateException {
|
25 |
ilm |
497 |
if (root == null || conf == null)
|
|
|
498 |
throw new NullPointerException();
|
80 |
ilm |
499 |
synchronized (this.varLock) {
|
|
|
500 |
if (this.isSetup())
|
|
|
501 |
throw new IllegalStateException("Already setup");
|
|
|
502 |
assert this.modulesElements.isEmpty() && this.runningModules.isEmpty() && this.modulesComponents.isEmpty() : "Modules cannot start without root & conf";
|
|
|
503 |
this.setRoot(root);
|
|
|
504 |
this.conf = conf;
|
19 |
ilm |
505 |
}
|
|
|
506 |
}
|
|
|
507 |
|
80 |
ilm |
508 |
public synchronized final boolean isInited() {
|
|
|
509 |
return this.inited;
|
|
|
510 |
}
|
|
|
511 |
|
|
|
512 |
/**
|
|
|
513 |
* Initialise the module manager.
|
|
|
514 |
*
|
|
|
515 |
* @throws Exception if required modules couldn't be registered.
|
|
|
516 |
*/
|
|
|
517 |
public synchronized final void init() throws Exception {
|
|
|
518 |
if (!this.isSetup())
|
|
|
519 |
throw new IllegalStateException("Not setup");
|
|
|
520 |
// don't check this.inited, that way we could register additional elements
|
|
|
521 |
|
|
|
522 |
SQLPreferences.getPrefTable(this.getRoot());
|
|
|
523 |
|
151 |
ilm |
524 |
final List<ModuleReference> requiredModules = new ArrayList<>(this.getAdminRequiredModules());
|
|
|
525 |
final List<ModuleReference> dbRequiredModules = new ArrayList<>(this.getDBRequiredModules());
|
80 |
ilm |
526 |
// add modules previously chosen (before restart)
|
|
|
527 |
final File toInstallFile = this.getToInstallFile();
|
|
|
528 |
Set<ModuleReference> toInstall = Collections.emptySet();
|
|
|
529 |
Set<ModuleReference> userReferencesToInstall = Collections.emptySet();
|
|
|
530 |
boolean persistent = false;
|
|
|
531 |
|
|
|
532 |
ModuleState toInstallTargetState = ModuleState.NOT_CREATED;
|
|
|
533 |
if (toInstallFile.exists()) {
|
|
|
534 |
if (!toInstallFile.canRead() || !toInstallFile.isFile()) {
|
|
|
535 |
L.warning("Couldn't read " + toInstallFile);
|
|
|
536 |
} else {
|
156 |
ilm |
537 |
try (final XMLDecoder dec = new XMLDecoder(new FileInputStream(toInstallFile))) {
|
80 |
ilm |
538 |
final Number version = (Number) dec.readObject();
|
93 |
ilm |
539 |
if (version.intValue() != TO_INSTALL_VERSION.intValue())
|
80 |
ilm |
540 |
throw new Exception("Version mismatch, expected " + TO_INSTALL_VERSION + " found " + version);
|
|
|
541 |
final Date fileDate = (Date) dec.readObject();
|
|
|
542 |
@SuppressWarnings("unchecked")
|
|
|
543 |
final Set<ModuleReference> toInstallUnsafe = (Set<ModuleReference>) dec.readObject();
|
|
|
544 |
@SuppressWarnings("unchecked")
|
|
|
545 |
final Set<ModuleReference> userReferencesToInstallUnsafe = (Set<ModuleReference>) dec.readObject();
|
|
|
546 |
toInstallTargetState = (ModuleState) dec.readObject();
|
|
|
547 |
persistent = (Boolean) dec.readObject();
|
|
|
548 |
try {
|
|
|
549 |
final Object extra = dec.readObject();
|
|
|
550 |
assert false : "Extra object " + extra;
|
|
|
551 |
} catch (ArrayIndexOutOfBoundsException e) {
|
|
|
552 |
// OK
|
|
|
553 |
}
|
|
|
554 |
|
|
|
555 |
final Date now = new Date();
|
|
|
556 |
if (fileDate.compareTo(now) > 0) {
|
|
|
557 |
L.warning("File is in the future : " + fileDate);
|
|
|
558 |
// check less than 2 hours
|
|
|
559 |
} else if (now.getTime() - fileDate.getTime() > 2 * 3600 * 1000) {
|
|
|
560 |
L.warning("File is too old : " + fileDate);
|
|
|
561 |
} else {
|
|
|
562 |
// no need to check that remote and local installed haven't changed since
|
|
|
563 |
// we're using ONLY_INSTALL_ARGUMENTS
|
|
|
564 |
toInstall = toInstallUnsafe;
|
|
|
565 |
userReferencesToInstall = userReferencesToInstallUnsafe;
|
|
|
566 |
if (toInstallTargetState.compareTo(ModuleState.REGISTERED) < 0)
|
|
|
567 |
L.warning("Forcing state to " + ModuleState.REGISTERED);
|
|
|
568 |
}
|
|
|
569 |
} catch (Exception e) {
|
|
|
570 |
// move out file to allow the next init() to succeed
|
|
|
571 |
final File errorFile = FileUtils.addSuffix(toInstallFile, ".error");
|
|
|
572 |
errorFile.delete();
|
|
|
573 |
final boolean renamed = toInstallFile.renameTo(errorFile);
|
|
|
574 |
throw new Exception("Couldn't parse " + toInstallFile + " ; renamed : " + renamed, e);
|
|
|
575 |
} finally {
|
156 |
ilm |
576 |
// Either the installation will succeed and we don't want to execute it again,
|
|
|
577 |
// or the installation will fail and we don't want to be caught in an endless
|
|
|
578 |
// loop (since there's no API to remove this file).
|
|
|
579 |
if (toInstallFile.exists() && !toInstallFile.delete())
|
|
|
580 |
throw new IOException("Couldn't delete " + toInstallFile);
|
80 |
ilm |
581 |
}
|
|
|
582 |
}
|
|
|
583 |
}
|
151 |
ilm |
584 |
// handle upgrades : the old version was not uninstalled and thus still in
|
|
|
585 |
// dbRequiredModules, and the new version is in toInstall, so the DepSolver will error out
|
|
|
586 |
// since both can't be installed at the same time.
|
|
|
587 |
for (final ModuleReference toInstallRef : toInstall) {
|
|
|
588 |
final Iterator<ModuleReference> iter = dbRequiredModules.iterator();
|
|
|
589 |
while (iter.hasNext()) {
|
|
|
590 |
final ModuleReference dbReqRef = iter.next();
|
|
|
591 |
if (dbReqRef.getID().equals(toInstallRef.getID())) {
|
|
|
592 |
L.config("Ignoring DB required " + dbReqRef + " because " + toInstallRef + " was requested from the last exit");
|
|
|
593 |
iter.remove();
|
|
|
594 |
}
|
|
|
595 |
}
|
|
|
596 |
}
|
|
|
597 |
requiredModules.addAll(dbRequiredModules);
|
80 |
ilm |
598 |
requiredModules.addAll(toInstall);
|
|
|
599 |
|
|
|
600 |
// if there's some choice to make, let the user make it
|
|
|
601 |
final Tuple2<Solutions, ModulesStateChangeResult> modules = this.createModules(requiredModules, NoChoicePredicate.ONLY_INSTALL_ARGUMENTS, ModuleState.REGISTERED);
|
|
|
602 |
if (modules.get1().getNotCreated().size() > 0)
|
|
|
603 |
throw new Exception("Impossible de créer les modules, not solved : " + modules.get0().getNotSolvedReferences() + " ; not created : " + modules.get1().getNotCreated());
|
|
|
604 |
if (toInstallTargetState.compareTo(ModuleState.STARTED) >= 0) {
|
|
|
605 |
// make them start by startPreviouslyRunningModules() (avoiding invokeLater() of
|
|
|
606 |
// createModules())
|
|
|
607 |
if (persistent) {
|
|
|
608 |
setPersistentModules(userReferencesToInstall);
|
|
|
609 |
} else {
|
|
|
610 |
// NO_CHANGE since they were just installed above
|
|
|
611 |
this.createModules(userReferencesToInstall, NoChoicePredicate.NO_CHANGE, ModuleState.STARTED, persistent);
|
|
|
612 |
}
|
|
|
613 |
}
|
|
|
614 |
this.inited = true;
|
|
|
615 |
}
|
|
|
616 |
|
|
|
617 |
// whether module removal can exit the VM
|
|
|
618 |
final void setExitAllowed(boolean exitAllowed) {
|
|
|
619 |
synchronized (this.varLock) {
|
|
|
620 |
this.exitAllowed = exitAllowed;
|
|
|
621 |
}
|
|
|
622 |
}
|
|
|
623 |
|
|
|
624 |
final boolean isExitAllowed() {
|
|
|
625 |
synchronized (this.varLock) {
|
|
|
626 |
return this.exitAllowed;
|
|
|
627 |
}
|
|
|
628 |
}
|
|
|
629 |
|
|
|
630 |
public final boolean needExit(final ModulesStateChange solution) {
|
151 |
ilm |
631 |
final Set<ModuleReference> refsToRemove = solution.getReferencesToRemove();
|
|
|
632 |
if (!refsToRemove.isEmpty()) {
|
|
|
633 |
// only need to exit if the module is loaded into memory
|
|
|
634 |
final Set<ModuleReference> registeredModules = this.getRegisteredModules();
|
|
|
635 |
for (final ModuleReference toRemove : refsToRemove) {
|
|
|
636 |
if (registeredModules.contains(toRemove))
|
|
|
637 |
return true;
|
|
|
638 |
}
|
|
|
639 |
}
|
|
|
640 |
return false;
|
80 |
ilm |
641 |
}
|
|
|
642 |
|
61 |
ilm |
643 |
// Preferences is thread-safe
|
18 |
ilm |
644 |
private Preferences getPrefs() {
|
|
|
645 |
// modules are installed per business entity (perhaps we could add a per user option, i.e.
|
|
|
646 |
// for all businesses of all databases)
|
|
|
647 |
final StringBuilder path = new StringBuilder(32);
|
25 |
ilm |
648 |
for (final String item : DBFileCache.getJDBCAncestorNames(getRoot(), true)) {
|
19 |
ilm |
649 |
path.append(StringUtils.getBoundedLengthString(DBItemFileCache.encode(item), Preferences.MAX_NAME_LENGTH));
|
18 |
ilm |
650 |
path.append('/');
|
|
|
651 |
}
|
|
|
652 |
// path must not end with '/'
|
|
|
653 |
path.setLength(path.length() - 1);
|
|
|
654 |
return Preferences.userNodeForPackage(ModuleManager.class).node(path.toString());
|
|
|
655 |
}
|
|
|
656 |
|
80 |
ilm |
657 |
// only put references passed by the user. That way if he installs module 'A' which depends on
|
|
|
658 |
// module 'util' and he later upgrade 'A' to a version which doesn't need 'util' anymore it
|
|
|
659 |
// won't get started. MAYBE even offer an "auto remove" feature.
|
18 |
ilm |
660 |
private Preferences getRunningIDsPrefs() {
|
|
|
661 |
return getPrefs().node("toRun");
|
|
|
662 |
}
|
|
|
663 |
|
80 |
ilm |
664 |
protected final Preferences getDBPrefs() {
|
|
|
665 |
synchronized (this) {
|
|
|
666 |
if (this.dbPrefs == null) {
|
|
|
667 |
final DBRoot root = getRoot();
|
|
|
668 |
if (root != null)
|
|
|
669 |
this.dbPrefs = (SQLPreferences) new SQLPreferences(root).node("modules");
|
|
|
670 |
}
|
|
|
671 |
return this.dbPrefs;
|
|
|
672 |
}
|
27 |
ilm |
673 |
}
|
|
|
674 |
|
80 |
ilm |
675 |
private final Preferences getRequiredIDsPrefs() {
|
|
|
676 |
return getDBPrefs().node("required");
|
67 |
ilm |
677 |
}
|
|
|
678 |
|
18 |
ilm |
679 |
protected final boolean isModuleInstalledLocally(String id) {
|
156 |
ilm |
680 |
final File versionFile = getLocalVersionFile(id);
|
|
|
681 |
return versionFile != null && versionFile.exists();
|
18 |
ilm |
682 |
}
|
|
|
683 |
|
19 |
ilm |
684 |
protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
|
61 |
ilm |
685 |
synchronized (fileMutex) {
|
|
|
686 |
final File versionFile = getLocalVersionFile(id);
|
156 |
ilm |
687 |
if (versionFile != null && versionFile.exists()) {
|
61 |
ilm |
688 |
try {
|
|
|
689 |
return new ModuleVersion(Long.valueOf(FileUtils.read(versionFile)));
|
|
|
690 |
} catch (IOException e) {
|
|
|
691 |
throw new IllegalStateException("Couldn't get installed version of " + id, e);
|
|
|
692 |
}
|
|
|
693 |
} else {
|
|
|
694 |
return null;
|
25 |
ilm |
695 |
}
|
|
|
696 |
}
|
19 |
ilm |
697 |
}
|
|
|
698 |
|
80 |
ilm |
699 |
public final Set<ModuleReference> getModulesInstalledLocally() {
|
|
|
700 |
return versionsMapToSet(getModulesVersionInstalledLocally());
|
|
|
701 |
}
|
|
|
702 |
|
|
|
703 |
public final Map<String, ModuleVersion> getModulesVersionInstalledLocally() {
|
61 |
ilm |
704 |
synchronized (fileMutex) {
|
|
|
705 |
final File dir = getLocalDirectory();
|
80 |
ilm |
706 |
if (!dir.isDirectory())
|
|
|
707 |
return Collections.emptyMap();
|
|
|
708 |
final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
|
61 |
ilm |
709 |
for (final File d : dir.listFiles()) {
|
|
|
710 |
final String id = d.getName();
|
|
|
711 |
final ModuleVersion version = getModuleVersionInstalledLocally(id);
|
80 |
ilm |
712 |
if (version != null)
|
|
|
713 |
res.put(id, version);
|
61 |
ilm |
714 |
}
|
|
|
715 |
return res;
|
19 |
ilm |
716 |
}
|
|
|
717 |
}
|
|
|
718 |
|
80 |
ilm |
719 |
private void setModuleInstalledLocally(ModuleReference f, boolean b) {
|
25 |
ilm |
720 |
try {
|
61 |
ilm |
721 |
synchronized (fileMutex) {
|
80 |
ilm |
722 |
if (b) {
|
|
|
723 |
final ModuleVersion vers = f.getVersion();
|
|
|
724 |
vers.checkValidity();
|
|
|
725 |
final File versionFile = getLocalVersionFile(f.getID());
|
|
|
726 |
FileUtils.mkdir_p(versionFile.getParentFile());
|
|
|
727 |
FileUtils.write(String.valueOf(vers.getMerged()), versionFile);
|
|
|
728 |
} else {
|
|
|
729 |
// perhaps add a parameter to only remove the versionFile
|
|
|
730 |
FileUtils.rm_R(getLocalDirectory(f.getID()));
|
|
|
731 |
}
|
25 |
ilm |
732 |
}
|
|
|
733 |
} catch (IOException e) {
|
|
|
734 |
throw new IllegalStateException("Couldn't change installed status of " + f, e);
|
18 |
ilm |
735 |
}
|
|
|
736 |
}
|
|
|
737 |
|
80 |
ilm |
738 |
private SQLTable getInstalledTable(final DBRoot r) throws SQLException {
|
|
|
739 |
synchronized (FWK_MODULE_TABLENAME) {
|
|
|
740 |
final List<SQLCreateTable> createTables = new ArrayList<SQLCreateTable>(4);
|
|
|
741 |
final SQLCreateTable createTable;
|
|
|
742 |
if (!r.contains(FWK_MODULE_TABLENAME)) {
|
|
|
743 |
// store :
|
|
|
744 |
// - currently installed module (TABLE_COLNAME & FIELD_COLNAME are null)
|
|
|
745 |
// - created tables (FIELD_COLNAME is null)
|
|
|
746 |
// - created fields (and whether they are keys)
|
|
|
747 |
createTable = new SQLCreateTable(r, FWK_MODULE_TABLENAME);
|
|
|
748 |
createTable.setPlain(true);
|
|
|
749 |
// let SQLCreateTable know which column is the primary key so that createDepTable
|
|
|
750 |
// can refer to it
|
|
|
751 |
createTable.addColumn(SQLSyntax.ID_NAME, createTable.getSyntax().getPrimaryIDDefinitionShort());
|
|
|
752 |
createTable.setPrimaryKey(SQLSyntax.ID_NAME);
|
|
|
753 |
createTable.addVarCharColumn(MODULE_COLNAME, 128);
|
|
|
754 |
createTable.addColumn(TABLE_COLNAME, "varchar(128) NULL");
|
|
|
755 |
createTable.addColumn(FIELD_COLNAME, "varchar(128) NULL");
|
|
|
756 |
createTable.addColumn(ISKEY_COLNAME, "boolean NULL");
|
|
|
757 |
createTable.addColumn(MODULE_VERSION_COLNAME, "bigint NOT NULL");
|
|
|
758 |
|
|
|
759 |
createTable.addUniqueConstraint("uniqModule", Arrays.asList(MODULE_COLNAME, TABLE_COLNAME, FIELD_COLNAME));
|
|
|
760 |
createTables.add(createTable);
|
|
|
761 |
} else {
|
|
|
762 |
createTable = null;
|
|
|
763 |
}
|
|
|
764 |
if (!r.contains(FWK_MODULE_DEP_TABLENAME)) {
|
|
|
765 |
final SQLCreateTable createDepTable = new SQLCreateTable(r, FWK_MODULE_DEP_TABLENAME);
|
|
|
766 |
createDepTable.setPlain(true);
|
|
|
767 |
final ForeignColSpec fk, fkNeeded;
|
|
|
768 |
if (createTable != null) {
|
|
|
769 |
fk = ForeignColSpec.fromCreateTable(createTable);
|
|
|
770 |
fkNeeded = ForeignColSpec.fromCreateTable(createTable);
|
|
|
771 |
} else {
|
|
|
772 |
final SQLTable moduleT = r.getTable(FWK_MODULE_TABLENAME);
|
|
|
773 |
fk = ForeignColSpec.fromTable(moduleT);
|
|
|
774 |
fkNeeded = ForeignColSpec.fromTable(moduleT);
|
|
|
775 |
}
|
|
|
776 |
// if we remove a module, remove it dependencies
|
|
|
777 |
createDepTable.addForeignColumn(fk.setColumnName(NEEDING_MODULE_COLNAME), Rule.CASCADE, Rule.CASCADE);
|
|
|
778 |
// if we try to remove a module that is needed, fail
|
|
|
779 |
createDepTable.addForeignColumn(fkNeeded.setColumnName(NEEDED_MODULE_COLNAME), Rule.CASCADE, Rule.RESTRICT);
|
|
|
780 |
|
|
|
781 |
createDepTable.setPrimaryKey(NEEDING_MODULE_COLNAME, NEEDED_MODULE_COLNAME);
|
|
|
782 |
createTables.add(createDepTable);
|
|
|
783 |
}
|
|
|
784 |
r.createTables(createTables);
|
18 |
ilm |
785 |
}
|
80 |
ilm |
786 |
return r.getTable(FWK_MODULE_TABLENAME);
|
18 |
ilm |
787 |
}
|
|
|
788 |
|
80 |
ilm |
789 |
private final SQLTable getDepTable() {
|
|
|
790 |
return getRoot().getTable(FWK_MODULE_DEP_TABLENAME);
|
|
|
791 |
}
|
|
|
792 |
|
|
|
793 |
public final DBRoot getRoot() {
|
|
|
794 |
synchronized (this.varLock) {
|
|
|
795 |
return this.root;
|
|
|
796 |
}
|
|
|
797 |
}
|
|
|
798 |
|
25 |
ilm |
799 |
private SQLDataSource getDS() {
|
|
|
800 |
return getRoot().getDBSystemRoot().getDataSource();
|
|
|
801 |
}
|
|
|
802 |
|
80 |
ilm |
803 |
public final Configuration getConf() {
|
|
|
804 |
synchronized (this.varLock) {
|
|
|
805 |
return this.conf;
|
|
|
806 |
}
|
25 |
ilm |
807 |
}
|
|
|
808 |
|
|
|
809 |
private SQLElementDirectory getDirectory() {
|
|
|
810 |
return getConf().getDirectory();
|
|
|
811 |
}
|
|
|
812 |
|
156 |
ilm |
813 |
public final File getLocalDirectory() {
|
25 |
ilm |
814 |
return new File(this.getConf().getConfDir(getRoot()), "modules");
|
|
|
815 |
}
|
|
|
816 |
|
156 |
ilm |
817 |
public final Set<String> migrateOldTransientDirs() {
|
|
|
818 |
Set<String> res = Collections.emptySet();
|
|
|
819 |
final File[] listFiles = this.getLocalDirectory().listFiles();
|
|
|
820 |
if (listFiles != null) {
|
|
|
821 |
for (final File f : listFiles) {
|
|
|
822 |
if (f.isDirectory()) {
|
|
|
823 |
res = migrateOldDir(res, f, OLD_BACKUP_DIR_SUFFIX);
|
|
|
824 |
res = migrateOldDir(res, f, OLD_FAILED_DIR_SUFFIX);
|
|
|
825 |
}
|
|
|
826 |
}
|
|
|
827 |
}
|
|
|
828 |
return res;
|
|
|
829 |
}
|
|
|
830 |
|
|
|
831 |
private final String getOldID(final File f, final String oldSuffix) {
|
|
|
832 |
if (f.getName().endsWith(oldSuffix)) {
|
|
|
833 |
final String id = f.getName().substring(0, f.getName().length() - oldSuffix.length());
|
|
|
834 |
return ModuleReference.checkID(id, false);
|
|
|
835 |
}
|
|
|
836 |
return null;
|
|
|
837 |
}
|
|
|
838 |
|
|
|
839 |
private final Set<String> migrateOldDir(Set<String> res, final File f, final String oldSuffix) {
|
|
|
840 |
final String id = getOldID(f, oldSuffix);
|
|
|
841 |
if (id != null) {
|
|
|
842 |
final Path localFailedDir = getLocalFailedDirectory(id);
|
|
|
843 |
try {
|
|
|
844 |
if (Files.exists(localFailedDir)) {
|
|
|
845 |
// newer already exists, remove old one
|
|
|
846 |
FileUtils.rm_R(f);
|
|
|
847 |
} else {
|
|
|
848 |
Files.createDirectories(localFailedDir.getParent());
|
|
|
849 |
Files.move(f.toPath(), localFailedDir);
|
|
|
850 |
}
|
|
|
851 |
} catch (IOException e) {
|
|
|
852 |
L.log(Level.CONFIG, "Couldn't migrate " + f, e);
|
|
|
853 |
if (res.isEmpty())
|
|
|
854 |
res = new HashSet<>();
|
|
|
855 |
res.add(id);
|
|
|
856 |
}
|
|
|
857 |
}
|
|
|
858 |
return res;
|
|
|
859 |
}
|
|
|
860 |
|
80 |
ilm |
861 |
// file specifying which module (and only those, dependencies won't be installed automatically)
|
|
|
862 |
// to install during the next application launch.
|
|
|
863 |
private final File getToInstallFile() {
|
|
|
864 |
return new File(getLocalDirectory(), "toInstall");
|
|
|
865 |
}
|
|
|
866 |
|
156 |
ilm |
867 |
private final File getLocalDataSubDir(final File f) {
|
|
|
868 |
// TODO return "Local Data" and a migration method
|
|
|
869 |
return f;
|
|
|
870 |
}
|
|
|
871 |
|
|
|
872 |
// => create "internal state" and "local data" folders inside getLocalDirectory() (i.e.
|
|
|
873 |
// module.id/local data and not local data/module.id so that we can easily copy to
|
|
|
874 |
// getLocalBackupDirectory() or uninstall a module)
|
|
|
875 |
|
|
|
876 |
private final Path getInternalStateSubDir(final Path f) {
|
|
|
877 |
// TODO return "Internal State" and a migration method
|
|
|
878 |
return f;
|
|
|
879 |
}
|
|
|
880 |
|
|
|
881 |
protected final File getLocalDataDirectory(final String id) {
|
|
|
882 |
return getLocalDataSubDir(this.getLocalDirectory(id));
|
|
|
883 |
}
|
|
|
884 |
|
|
|
885 |
private final Path getInternalStateDirectory(final String id) {
|
|
|
886 |
final File l = this.getLocalDirectory(id);
|
|
|
887 |
return l == null ? null : getInternalStateSubDir(l.toPath());
|
|
|
888 |
}
|
|
|
889 |
|
|
|
890 |
private final File getLocalDirectory(final String id) {
|
|
|
891 |
if (ModuleReference.checkID(id, false) == null)
|
|
|
892 |
return null;
|
25 |
ilm |
893 |
return new File(this.getLocalDirectory(), id);
|
|
|
894 |
}
|
|
|
895 |
|
156 |
ilm |
896 |
// contains a copy of local module data during install
|
|
|
897 |
protected final Path getLocalBackupDirectory(final String id) {
|
|
|
898 |
// will be ignored by getModulesVersionInstalledLocally()
|
|
|
899 |
assert ModuleReference.checkID(BACKUP_DIR, false) == null;
|
|
|
900 |
return this.getLocalDirectory().toPath().resolve(BACKUP_DIR).resolve(id);
|
|
|
901 |
}
|
|
|
902 |
|
|
|
903 |
// contains local module data of the last failed install
|
|
|
904 |
protected final Path getLocalFailedDirectory(final String id) {
|
|
|
905 |
// will be ignored by getModulesVersionInstalledLocally()
|
|
|
906 |
assert ModuleReference.checkID(FAILED_DIR, false) == null;
|
|
|
907 |
return this.getLocalDirectory().toPath().resolve(FAILED_DIR).resolve(id);
|
|
|
908 |
}
|
|
|
909 |
|
25 |
ilm |
910 |
private final File getLocalVersionFile(final String id) {
|
156 |
ilm |
911 |
final Path dir = this.getInternalStateDirectory(id);
|
|
|
912 |
if (dir == null)
|
|
|
913 |
return null;
|
|
|
914 |
return new File(dir.toFile(), "version");
|
25 |
ilm |
915 |
}
|
|
|
916 |
|
80 |
ilm |
917 |
public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
|
|
|
918 |
return getDBInstalledModules(id).get(id);
|
18 |
ilm |
919 |
}
|
|
|
920 |
|
80 |
ilm |
921 |
public final Set<ModuleReference> getModulesInstalledRemotely() throws SQLException {
|
|
|
922 |
return getDBInstalledModuleRowsByRef(null).keySet();
|
|
|
923 |
}
|
18 |
ilm |
924 |
|
80 |
ilm |
925 |
public final Map<String, ModuleVersion> getDBInstalledModules() throws SQLException {
|
|
|
926 |
return getDBInstalledModules(null);
|
|
|
927 |
}
|
73 |
ilm |
928 |
|
80 |
ilm |
929 |
private final Where getModuleRowWhere(final TableRef installedTable) throws SQLException {
|
|
|
930 |
return Where.isNull(installedTable.getField(TABLE_COLNAME)).and(Where.isNull(installedTable.getField(FIELD_COLNAME)));
|
|
|
931 |
}
|
67 |
ilm |
932 |
|
80 |
ilm |
933 |
private final List<SQLRow> getDBInstalledModuleRows(final String id) throws SQLException {
|
|
|
934 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
935 |
final SQLSelect sel = new SQLSelect().addSelectStar(installedTable);
|
|
|
936 |
sel.setWhere(getModuleRowWhere(installedTable));
|
|
|
937 |
if (id != null)
|
|
|
938 |
sel.andWhere(new Where(installedTable.getField(MODULE_COLNAME), "=", id));
|
|
|
939 |
return SQLRowListRSH.execute(sel);
|
|
|
940 |
}
|
67 |
ilm |
941 |
|
80 |
ilm |
942 |
private final ModuleReference getRef(final SQLRow r) throws SQLException {
|
|
|
943 |
return new ModuleReference(r.getString(MODULE_COLNAME), new ModuleVersion(r.getLong(MODULE_VERSION_COLNAME)));
|
|
|
944 |
}
|
|
|
945 |
|
|
|
946 |
private final Map<String, ModuleVersion> getDBInstalledModules(final String id) throws SQLException {
|
|
|
947 |
final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
|
|
|
948 |
for (final SQLRow r : getDBInstalledModuleRows(id)) {
|
|
|
949 |
final ModuleReference ref = getRef(r);
|
|
|
950 |
res.put(ref.getID(), ref.getVersion());
|
18 |
ilm |
951 |
}
|
80 |
ilm |
952 |
return res;
|
|
|
953 |
}
|
18 |
ilm |
954 |
|
80 |
ilm |
955 |
private final Map<ModuleReference, SQLRow> getDBInstalledModuleRowsByRef(final String id) throws SQLException {
|
|
|
956 |
final Map<ModuleReference, SQLRow> res = new HashMap<ModuleReference, SQLRow>();
|
|
|
957 |
for (final SQLRow r : getDBInstalledModuleRows(id)) {
|
|
|
958 |
res.put(getRef(r), r);
|
|
|
959 |
}
|
|
|
960 |
return res;
|
19 |
ilm |
961 |
}
|
|
|
962 |
|
80 |
ilm |
963 |
private SQLRow setDBInstalledModule(ModuleReference f, boolean b) throws SQLException {
|
|
|
964 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
965 |
final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", f.getID());
|
|
|
966 |
final Where w = idW.and(getModuleRowWhere(installedTable));
|
|
|
967 |
if (b) {
|
|
|
968 |
final SQLSelect sel = new SQLSelect();
|
|
|
969 |
sel.addSelect(installedTable.getKey());
|
|
|
970 |
sel.setWhere(w);
|
|
|
971 |
final Number id = (Number) installedTable.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
|
|
|
972 |
final SQLRowValues vals = new SQLRowValues(installedTable);
|
|
|
973 |
vals.put(MODULE_VERSION_COLNAME, f.getVersion().getMerged());
|
|
|
974 |
if (id != null) {
|
|
|
975 |
vals.setID(id);
|
|
|
976 |
return vals.update();
|
|
|
977 |
} else {
|
|
|
978 |
vals.put(MODULE_COLNAME, f.getID());
|
|
|
979 |
vals.put(TABLE_COLNAME, null);
|
|
|
980 |
vals.put(FIELD_COLNAME, null);
|
|
|
981 |
return vals.insert();
|
|
|
982 |
}
|
|
|
983 |
} else {
|
|
|
984 |
installedTable.getDBSystemRoot().getDataSource().execute("DELETE FROM " + installedTable.getSQLName().quote() + " WHERE " + w.getClause());
|
|
|
985 |
installedTable.fireTableModified(SQLRow.NONEXISTANT_ID);
|
|
|
986 |
return null;
|
|
|
987 |
}
|
|
|
988 |
}
|
19 |
ilm |
989 |
|
80 |
ilm |
990 |
public final Tuple2<Set<String>, Set<SQLName>> getCreatedItems(final String id) throws SQLException {
|
|
|
991 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
992 |
final SQLSelect sel = new SQLSelect();
|
|
|
993 |
sel.addSelect(installedTable.getKey());
|
|
|
994 |
sel.addSelect(installedTable.getField(TABLE_COLNAME));
|
|
|
995 |
sel.addSelect(installedTable.getField(FIELD_COLNAME));
|
|
|
996 |
sel.setWhere(new Where(installedTable.getField(MODULE_COLNAME), "=", id).and(Where.isNotNull(installedTable.getField(TABLE_COLNAME))));
|
|
|
997 |
final Set<String> tables = new HashSet<String>();
|
|
|
998 |
final Set<SQLName> fields = new HashSet<SQLName>();
|
|
|
999 |
for (final SQLRow r : SQLRowListRSH.execute(sel)) {
|
|
|
1000 |
final String tableName = r.getString(TABLE_COLNAME);
|
|
|
1001 |
final String fieldName = r.getString(FIELD_COLNAME);
|
|
|
1002 |
if (fieldName == null)
|
|
|
1003 |
tables.add(tableName);
|
|
|
1004 |
else
|
|
|
1005 |
fields.add(new SQLName(tableName, fieldName));
|
|
|
1006 |
}
|
|
|
1007 |
return Tuple2.create(tables, fields);
|
|
|
1008 |
}
|
|
|
1009 |
|
|
|
1010 |
private void updateModuleFields(ModuleFactory factory, DepSolverGraph graph, final DBContext ctxt) throws SQLException {
|
|
|
1011 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
1012 |
final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", factory.getID());
|
|
|
1013 |
// removed items
|
|
|
1014 |
{
|
|
|
1015 |
final List<Where> dropWheres = new ArrayList<Where>();
|
|
|
1016 |
for (final String dropped : ctxt.getRemovedTables()) {
|
|
|
1017 |
dropWheres.add(new Where(installedTable.getField(TABLE_COLNAME), "=", dropped));
|
|
|
1018 |
}
|
|
|
1019 |
for (final SQLName dropped : ctxt.getRemovedFieldsFromExistingTables()) {
|
|
|
1020 |
dropWheres.add(new Where(installedTable.getField(TABLE_COLNAME), "=", dropped.getItem(0)).and(new Where(installedTable.getField(FIELD_COLNAME), "=", dropped.getItem(1))));
|
|
|
1021 |
}
|
|
|
1022 |
if (dropWheres.size() > 0)
|
|
|
1023 |
installedTable.getDBSystemRoot().getDataSource().execute("DELETE FROM " + installedTable.getSQLName().quote() + " WHERE " + Where.or(dropWheres).and(idW).getClause());
|
|
|
1024 |
}
|
|
|
1025 |
// added items
|
|
|
1026 |
{
|
|
|
1027 |
final SQLRowValues vals = new SQLRowValues(installedTable);
|
|
|
1028 |
vals.put(MODULE_VERSION_COLNAME, factory.getVersion().getMerged());
|
|
|
1029 |
vals.put(MODULE_COLNAME, factory.getID());
|
|
|
1030 |
for (final String added : ctxt.getAddedTables()) {
|
|
|
1031 |
vals.put(TABLE_COLNAME, added).put(FIELD_COLNAME, null).insert();
|
|
|
1032 |
final SQLTable t = ctxt.getRoot().findTable(added);
|
83 |
ilm |
1033 |
if (t == null) {
|
|
|
1034 |
throw new IllegalStateException("Unable to find added table " + added + " in root " + ctxt.getRoot().getName());
|
|
|
1035 |
}
|
80 |
ilm |
1036 |
for (final SQLField field : t.getFields()) {
|
|
|
1037 |
vals.put(TABLE_COLNAME, added).put(FIELD_COLNAME, field.getName()).put(ISKEY_COLNAME, field.isKey()).insert();
|
67 |
ilm |
1038 |
}
|
80 |
ilm |
1039 |
vals.remove(ISKEY_COLNAME);
|
19 |
ilm |
1040 |
}
|
80 |
ilm |
1041 |
for (final SQLName added : ctxt.getAddedFieldsToExistingTables()) {
|
|
|
1042 |
final SQLTable t = ctxt.getRoot().findTable(added.getItem(0));
|
|
|
1043 |
final SQLField field = t.getField(added.getItem(1));
|
|
|
1044 |
vals.put(TABLE_COLNAME, t.getName()).put(FIELD_COLNAME, field.getName()).put(ISKEY_COLNAME, field.isKey()).insert();
|
|
|
1045 |
}
|
|
|
1046 |
vals.remove(ISKEY_COLNAME);
|
|
|
1047 |
}
|
|
|
1048 |
// Always put true, even if getCreatedItems() is empty, since for now we can't be sure that
|
|
|
1049 |
// the module didn't insert rows or otherwise changed the DB (MAYBE change SQLDataSource to
|
|
|
1050 |
// hand out connections with read only user for a new ThreadGroup, or even no connections at
|
|
|
1051 |
// all). If we could assert that the module didn't access at all the DB, we could add an
|
|
|
1052 |
// option so that the module can declare not accessing the DB and install() would know that
|
|
|
1053 |
// the DB version of the module is null. This could be beneficial since different users
|
|
|
1054 |
// could install different version of modules that only change the UI.
|
|
|
1055 |
final SQLRow moduleRow = setDBInstalledModule(factory.getReference(), true);
|
19 |
ilm |
1056 |
|
80 |
ilm |
1057 |
// update dependencies
|
|
|
1058 |
final SQLTable depT = getDepTable();
|
|
|
1059 |
depT.getDBSystemRoot().getDataSource().execute("DELETE FROM " + depT.getSQLName().quote() + " WHERE " + new Where(depT.getField(NEEDING_MODULE_COLNAME), "=", moduleRow.getID()).getClause());
|
|
|
1060 |
depT.fireTableModified(SQLRow.NONEXISTANT_ID);
|
|
|
1061 |
final SQLRowValues vals = new SQLRowValues(depT).put(NEEDING_MODULE_COLNAME, moduleRow.getID());
|
|
|
1062 |
final Map<ModuleReference, SQLRow> moduleRows = getDBInstalledModuleRowsByRef(null);
|
|
|
1063 |
for (final ModuleFactory dep : graph.getDependencies(factory).values()) {
|
|
|
1064 |
vals.put(NEEDED_MODULE_COLNAME, moduleRows.get(dep.getReference()).getID()).insertVerbatim();
|
|
|
1065 |
}
|
|
|
1066 |
}
|
19 |
ilm |
1067 |
|
80 |
ilm |
1068 |
private void removeModuleFields(ModuleReference f) throws SQLException {
|
|
|
1069 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
1070 |
final Where idW = new Where(installedTable.getField(MODULE_COLNAME), "=", f.getID());
|
|
|
1071 |
installedTable.getDBSystemRoot().getDataSource()
|
|
|
1072 |
.execute("DELETE FROM " + installedTable.getSQLName().quote() + " WHERE " + Where.isNotNull(installedTable.getField(TABLE_COLNAME)).and(idW).getClause());
|
|
|
1073 |
setDBInstalledModule(f, false);
|
|
|
1074 |
|
|
|
1075 |
// FWK_MODULE_DEP_TABLENAME rows removed with CASCADE
|
|
|
1076 |
getDepTable().fireTableModified(SQLRow.NONEXISTANT_ID);
|
|
|
1077 |
}
|
|
|
1078 |
|
|
|
1079 |
/**
|
|
|
1080 |
* Get the modules required because they have created a new table or new foreign key. E.g. if a
|
|
|
1081 |
* module created a child table, as long as the table is in the database it needs to be archived
|
|
|
1082 |
* along its parent.
|
|
|
1083 |
*
|
|
|
1084 |
* @return the modules.
|
|
|
1085 |
* @throws SQLException if an error occurs.
|
|
|
1086 |
*/
|
|
|
1087 |
final List<ModuleReference> getDBRequiredModules() throws SQLException {
|
|
|
1088 |
// modules which have created a table or a key
|
|
|
1089 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
1090 |
final AliasedTable installedTableVers = new AliasedTable(installedTable, "vers");
|
|
|
1091 |
final SQLSelect sel = new SQLSelect();
|
|
|
1092 |
sel.addSelect(installedTable.getField(MODULE_COLNAME));
|
|
|
1093 |
// for each row, get the version from the main row
|
|
|
1094 |
sel.addJoin("INNER", new Where(installedTable.getField(MODULE_COLNAME), "=", installedTableVers.getField(MODULE_COLNAME)).and(getModuleRowWhere(installedTableVers)));
|
|
|
1095 |
sel.addSelect(installedTableVers.getField(MODULE_VERSION_COLNAME));
|
|
|
1096 |
|
|
|
1097 |
final Where tableCreated = Where.isNotNull(installedTable.getField(TABLE_COLNAME)).and(Where.isNull(installedTable.getField(FIELD_COLNAME)));
|
|
|
1098 |
final Where keyCreated = Where.isNotNull(installedTable.getField(FIELD_COLNAME)).and(new Where(installedTable.getField(ISKEY_COLNAME), "=", Boolean.TRUE));
|
|
|
1099 |
sel.setWhere(tableCreated.or(keyCreated));
|
|
|
1100 |
sel.addGroupBy(installedTable.getField(MODULE_COLNAME));
|
|
|
1101 |
// allow to reference the field in the SELECT and shouldn't change anything since each
|
|
|
1102 |
// module has only one version
|
|
|
1103 |
sel.addGroupBy(installedTableVers.getField(MODULE_VERSION_COLNAME));
|
|
|
1104 |
@SuppressWarnings("unchecked")
|
|
|
1105 |
final List<Map<String, Object>> maps = (List<Map<String, Object>>) installedTable.getDBSystemRoot().getDataSource().execute(sel.asString());
|
|
|
1106 |
final List<ModuleReference> res = new ArrayList<ModuleReference>(maps.size());
|
|
|
1107 |
for (final Map<String, Object> m : maps) {
|
|
|
1108 |
final String moduleID = (String) m.get(MODULE_COLNAME);
|
|
|
1109 |
final ModuleVersion vers = new ModuleVersion(((Number) m.get(MODULE_VERSION_COLNAME)).longValue());
|
|
|
1110 |
res.add(new ModuleReference(moduleID, vers));
|
|
|
1111 |
}
|
|
|
1112 |
L.config("getDBRequiredModules() found " + res);
|
|
|
1113 |
return res;
|
|
|
1114 |
}
|
|
|
1115 |
|
|
|
1116 |
private void install(final AbstractModule module, final DepSolverGraph graph) throws Exception {
|
|
|
1117 |
assert Thread.holdsLock(this);
|
|
|
1118 |
final ModuleFactory factory = module.getFactory();
|
|
|
1119 |
final ModuleVersion localVersion = getModuleVersionInstalledLocally(factory.getID());
|
|
|
1120 |
final ModuleVersion lastInstalledVersion = getDBInstalledModuleVersion(factory.getID());
|
|
|
1121 |
final ModuleVersion moduleVersion = module.getFactory().getVersion();
|
|
|
1122 |
final boolean dbOK = moduleVersion.equals(lastInstalledVersion);
|
|
|
1123 |
|
|
|
1124 |
if (!dbOK && !currentUserIsAdmin())
|
|
|
1125 |
throw new IllegalStateException("Not allowed to install " + module.getFactory() + " in the database");
|
|
|
1126 |
|
|
|
1127 |
if (lastInstalledVersion != null && moduleVersion.compareTo(lastInstalledVersion) < 0)
|
|
|
1128 |
throw new IllegalArgumentException("Module older than the one installed in the DB : " + moduleVersion + " < " + lastInstalledVersion);
|
|
|
1129 |
if (localVersion != null && moduleVersion.compareTo(localVersion) < 0)
|
|
|
1130 |
throw new IllegalArgumentException("Module older than the one installed locally : " + moduleVersion + " < " + localVersion);
|
|
|
1131 |
if (!moduleVersion.equals(localVersion) || !dbOK) {
|
|
|
1132 |
// local
|
|
|
1133 |
final File localDir = getLocalDirectory(factory.getID());
|
|
|
1134 |
// There are 2 choices to handle the update of files :
|
|
|
1135 |
// 1. copy dir to a new one and pass it to DBContext, then either rename it to dir or
|
|
|
1136 |
// rename it failed
|
|
|
1137 |
// 2. copy dir to a backup, pass dir to DBContext, then either remove backup or rename
|
|
|
1138 |
// it to dir
|
|
|
1139 |
// Choice 2 is simpler since the module deals with the same directory in both install()
|
|
|
1140 |
// and start()
|
156 |
ilm |
1141 |
final Path backupDir;
|
80 |
ilm |
1142 |
// check if we need a backup
|
|
|
1143 |
if (localDir.exists()) {
|
156 |
ilm |
1144 |
backupDir = getLocalBackupDirectory(factory.getID());
|
|
|
1145 |
FileUtils.rm_R(backupDir, false);
|
|
|
1146 |
Files.createDirectories(backupDir.getParent());
|
|
|
1147 |
FileUtils.copyDirectory(localDir.toPath(), backupDir, false, StandardCopyOption.COPY_ATTRIBUTES);
|
80 |
ilm |
1148 |
} else {
|
|
|
1149 |
backupDir = null;
|
|
|
1150 |
FileUtils.mkdir_p(localDir);
|
|
|
1151 |
}
|
|
|
1152 |
assert localDir.exists();
|
|
|
1153 |
try {
|
|
|
1154 |
SQLUtils.executeAtomic(getDS(), new ConnectionHandlerNoSetup<Object, IOException>() {
|
|
|
1155 |
@Override
|
|
|
1156 |
public Object handle(SQLDataSource ds) throws SQLException, IOException {
|
|
|
1157 |
final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
|
156 |
ilm |
1158 |
final DBContext ctxt = new DBContext(getLocalDataSubDir(localDir), localVersion, getRoot(), lastInstalledVersion, alreadyCreatedItems.get0(), alreadyCreatedItems.get1(),
|
|
|
1159 |
getDirectory());
|
80 |
ilm |
1160 |
// install local (i.e. ctxt stores the actions to carry on the DB)
|
|
|
1161 |
// TODO pass a data source with no rights to modify the data definition (or
|
|
|
1162 |
// even no rights to modify the data if DB version is up to date)
|
|
|
1163 |
module.install(ctxt);
|
|
|
1164 |
if (!localDir.exists())
|
|
|
1165 |
throw new IOException("Modules shouldn't remove their directory");
|
|
|
1166 |
// install in DB
|
151 |
ilm |
1167 |
ctxt.executeSQL();
|
80 |
ilm |
1168 |
updateModuleFields(factory, graph, ctxt);
|
|
|
1169 |
return null;
|
25 |
ilm |
1170 |
}
|
80 |
ilm |
1171 |
});
|
|
|
1172 |
} catch (Exception e) {
|
|
|
1173 |
// install did not complete successfully
|
|
|
1174 |
if (getRoot().getServer().getSQLSystem() == SQLSystem.MYSQL)
|
|
|
1175 |
L.warning("MySQL cannot rollback DDL statements");
|
|
|
1176 |
// keep failed install files and restore previous files
|
156 |
ilm |
1177 |
final Path failed = getLocalFailedDirectory(factory.getID());
|
|
|
1178 |
boolean moved = false;
|
|
|
1179 |
try {
|
|
|
1180 |
FileUtils.rm_R(failed, false);
|
|
|
1181 |
Files.createDirectories(failed.getParent());
|
|
|
1182 |
Files.move(localDir.toPath(), failed);
|
|
|
1183 |
final String errorMsg = "Couldn't install " + module + " :\n" + ExceptionUtils.getStackTrace(e);
|
|
|
1184 |
// TODO as in getLocalVersionFile(), separate internal state (i.e. version and
|
|
|
1185 |
// error) from module local data
|
|
|
1186 |
Files.write(getInternalStateSubDir(failed).resolve("Install error.txt"), errorMsg.getBytes(StandardCharsets.UTF_8));
|
|
|
1187 |
moved = true;
|
|
|
1188 |
} catch (Exception e1) {
|
|
|
1189 |
L.log(Level.WARNING, "Couldn't move " + localDir + " to " + failed, e1);
|
|
|
1190 |
}
|
|
|
1191 |
// restore if needed
|
|
|
1192 |
if (moved && backupDir != null) {
|
80 |
ilm |
1193 |
assert !localDir.exists();
|
156 |
ilm |
1194 |
try {
|
|
|
1195 |
Files.move(backupDir, localDir.toPath());
|
|
|
1196 |
} catch (Exception e1) {
|
|
|
1197 |
L.log(Level.WARNING, "Couldn't restore " + backupDir + " to " + localDir, e1);
|
|
|
1198 |
}
|
18 |
ilm |
1199 |
}
|
80 |
ilm |
1200 |
throw e;
|
25 |
ilm |
1201 |
}
|
80 |
ilm |
1202 |
// DB transaction was committed, remove backup files
|
|
|
1203 |
assert localDir.exists();
|
|
|
1204 |
if (backupDir != null)
|
|
|
1205 |
FileUtils.rm_R(backupDir);
|
|
|
1206 |
setModuleInstalledLocally(factory.getReference(), true);
|
18 |
ilm |
1207 |
}
|
80 |
ilm |
1208 |
assert moduleVersion.equals(getModuleVersionInstalledLocally(factory.getID())) && moduleVersion.equals(getDBInstalledModuleVersion(factory.getID()));
|
18 |
ilm |
1209 |
}
|
|
|
1210 |
|
151 |
ilm |
1211 |
private void registerSQLElements(final AbstractModule module, Map<SQLTable, SQLElement> beforeElements) throws IOException {
|
80 |
ilm |
1212 |
final ModuleReference id = module.getFactory().getReference();
|
25 |
ilm |
1213 |
synchronized (this.modulesElements) {
|
80 |
ilm |
1214 |
// perhaps check that no other version of the module has been registered
|
25 |
ilm |
1215 |
if (!this.modulesElements.containsKey(id)) {
|
|
|
1216 |
final SQLElementDirectory dir = getDirectory();
|
|
|
1217 |
module.setupElements(dir);
|
80 |
ilm |
1218 |
final IdentityHashMap<SQLElement, SQLElement> elements = new IdentityHashMap<SQLElement, SQLElement>();
|
25 |
ilm |
1219 |
// use IdentitySet so as not to call equals() since it triggers initFF()
|
|
|
1220 |
final IdentitySet<SQLElement> beforeElementsSet = new IdentityHashSet<SQLElement>(beforeElements.values());
|
83 |
ilm |
1221 |
// copy to be able to restore elements while iterating
|
|
|
1222 |
final IdentitySet<SQLElement> afterElementsSet = new IdentityHashSet<SQLElement>(dir.getElements());
|
|
|
1223 |
for (final SQLElement elem : afterElementsSet) {
|
25 |
ilm |
1224 |
if (!beforeElementsSet.contains(elem)) {
|
80 |
ilm |
1225 |
if (!(elem instanceof ModuleElement))
|
|
|
1226 |
L.warning("Module added an element that isn't a ModuleElement : " + elem);
|
25 |
ilm |
1227 |
if (beforeElements.containsKey(elem.getTable())) {
|
80 |
ilm |
1228 |
final SQLElement replacedElem = beforeElements.get(elem.getTable());
|
|
|
1229 |
// Code safety : a module can make sure that its elements won't be
|
|
|
1230 |
// replaced. We thus require that elem is a subclass of replacedElem,
|
|
|
1231 |
// i.e. a module can use standard java access rules (e.g. package
|
|
|
1232 |
// private constructor, final method).
|
|
|
1233 |
final boolean codeSafe = replacedElem.getClass().isInstance(elem);
|
|
|
1234 |
|
83 |
ilm |
1235 |
final boolean mngrSafe = isMngrSafe(module, replacedElem);
|
|
|
1236 |
if (codeSafe && mngrSafe) {
|
80 |
ilm |
1237 |
// store replacedElem so that it can be restored in unregister()
|
|
|
1238 |
elements.put(elem, replacedElem);
|
|
|
1239 |
} else {
|
83 |
ilm |
1240 |
final List<String> pbs = new ArrayList<String>(2);
|
|
|
1241 |
if (!codeSafe)
|
|
|
1242 |
pbs.add(elem + " isn't a subclass of " + replacedElem);
|
|
|
1243 |
if (!mngrSafe)
|
|
|
1244 |
pbs.add(module + " doesn't depend on " + replacedElem);
|
|
|
1245 |
L.warning("Trying to replace element for " + elem.getTable() + " with " + elem + " but\n" + CollectionUtils.join(pbs, "\n"));
|
80 |
ilm |
1246 |
dir.addSQLElement(replacedElem);
|
|
|
1247 |
}
|
25 |
ilm |
1248 |
} else {
|
80 |
ilm |
1249 |
elements.put(elem, null);
|
25 |
ilm |
1250 |
}
|
|
|
1251 |
}
|
19 |
ilm |
1252 |
}
|
80 |
ilm |
1253 |
|
156 |
ilm |
1254 |
// Load translations after registering elements since SQLFieldTranslator needs them.
|
|
|
1255 |
// If setupElements() needs translations then perhaps we should store translations
|
|
|
1256 |
// with the element code, without needing the element.
|
|
|
1257 |
final String mdVariant = getMDVariant(module.getFactory());
|
|
|
1258 |
final Set<SQLTable> tablesWithMD = loadTranslations(getConf().getTranslator(), module, mdVariant);
|
|
|
1259 |
|
80 |
ilm |
1260 |
// insert just loaded labels into the search path
|
|
|
1261 |
for (final SQLTable tableWithDoc : tablesWithMD) {
|
|
|
1262 |
final SQLElement sqlElem = this.getDirectory().getElement(tableWithDoc);
|
|
|
1263 |
if (sqlElem == null)
|
|
|
1264 |
throw new IllegalStateException("Missing element for table with metadata : " + tableWithDoc);
|
|
|
1265 |
// avoid duplicates
|
|
|
1266 |
final boolean already = sqlElem instanceof ModuleElement && ((ModuleElement) sqlElem).getFactory() == module.getFactory();
|
|
|
1267 |
if (!already)
|
|
|
1268 |
sqlElem.addToMDPath(mdVariant);
|
|
|
1269 |
}
|
|
|
1270 |
|
25 |
ilm |
1271 |
this.modulesElements.put(id, elements);
|
19 |
ilm |
1272 |
}
|
|
|
1273 |
}
|
|
|
1274 |
}
|
|
|
1275 |
|
80 |
ilm |
1276 |
// Manager safety : when a module is unregistered, replacedElem can be restored. We thus require
|
|
|
1277 |
// that replacedElem was registered by one of our dependencies (or by the core application),
|
|
|
1278 |
// forcing a predictable order (or error if two unrelated modules want to replace the same
|
|
|
1279 |
// element).
|
|
|
1280 |
// FIXME modules are only unregistered when uninstalled (e.g. to know how to archive additional
|
|
|
1281 |
// fields), so even though a module is stopped its UI (getName(), getComboRequest(),
|
|
|
1282 |
// createComponent()) will still be used.
|
|
|
1283 |
private boolean isMngrSafe(final AbstractModule module, final SQLElement replacedElem) {
|
|
|
1284 |
final boolean mngrSafe;
|
|
|
1285 |
final ModuleReference moduleForElement = getModuleForElement(replacedElem);
|
|
|
1286 |
if (moduleForElement == null) {
|
|
|
1287 |
// module from core app
|
|
|
1288 |
mngrSafe = true;
|
|
|
1289 |
} else {
|
|
|
1290 |
// MAYBE handle non direct dependency
|
|
|
1291 |
final ModuleFactory replacedFactory = this.factories.getFactory(moduleForElement);
|
|
|
1292 |
mngrSafe = this.dependencyGraph.containsEdge(module.getFactory(), replacedFactory);
|
|
|
1293 |
}
|
|
|
1294 |
return mngrSafe;
|
|
|
1295 |
}
|
|
|
1296 |
|
|
|
1297 |
private final ModuleReference getModuleForElement(final SQLElement elem) {
|
|
|
1298 |
synchronized (this.modulesElements) {
|
|
|
1299 |
for (final Entry<ModuleReference, IdentityHashMap<SQLElement, SQLElement>> e : this.modulesElements.entrySet()) {
|
|
|
1300 |
final IdentityHashMap<SQLElement, SQLElement> map = e.getValue();
|
|
|
1301 |
assert map instanceof IdentityHashMap : "identity needed but got " + map.getClass();
|
|
|
1302 |
if (map.containsKey(elem))
|
|
|
1303 |
return e.getKey();
|
|
|
1304 |
}
|
|
|
1305 |
}
|
|
|
1306 |
return null;
|
|
|
1307 |
}
|
|
|
1308 |
|
|
|
1309 |
public final Set<ModuleReference> getRegisteredModules() {
|
|
|
1310 |
synchronized (this.modulesElements) {
|
|
|
1311 |
return new HashSet<ModuleReference>(this.modulesElements.keySet());
|
|
|
1312 |
}
|
|
|
1313 |
}
|
|
|
1314 |
|
151 |
ilm |
1315 |
final Set<SQLElement> getRegisteredElements(final ModuleReference ref) {
|
|
|
1316 |
synchronized (this.modulesElements) {
|
|
|
1317 |
final IdentityHashMap<SQLElement, SQLElement> map = this.modulesElements.get(ref);
|
|
|
1318 |
if (map == null || map.isEmpty())
|
|
|
1319 |
return Collections.emptySet();
|
|
|
1320 |
return Collections.unmodifiableSet(new HashSet<SQLElement>(map.keySet()));
|
|
|
1321 |
}
|
|
|
1322 |
}
|
|
|
1323 |
|
80 |
ilm |
1324 |
private void setupComponents(final AbstractModule module, final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems, final MenuAndActions ma) throws SQLException {
|
61 |
ilm |
1325 |
assert SwingUtilities.isEventDispatchThread();
|
19 |
ilm |
1326 |
final String id = module.getFactory().getID();
|
|
|
1327 |
if (!this.modulesComponents.containsKey(id)) {
|
25 |
ilm |
1328 |
final SQLElementDirectory dir = getDirectory();
|
80 |
ilm |
1329 |
final ComponentsContext ctxt = new ComponentsContext(dir, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
|
19 |
ilm |
1330 |
module.setupComponents(ctxt);
|
177 |
ilm |
1331 |
TranslationManager.addTranslationStreamFromClass(module.getClass());
|
73 |
ilm |
1332 |
this.setupMenu(module, ma);
|
19 |
ilm |
1333 |
this.modulesComponents.put(id, ctxt);
|
|
|
1334 |
}
|
|
|
1335 |
}
|
|
|
1336 |
|
80 |
ilm |
1337 |
final List<ModuleReference> getAdminRequiredModules() throws IOException {
|
|
|
1338 |
return this.getAdminRequiredModules(false);
|
|
|
1339 |
}
|
67 |
ilm |
1340 |
|
80 |
ilm |
1341 |
/**
|
|
|
1342 |
* Get the modules required by the administrator.
|
|
|
1343 |
*
|
|
|
1344 |
* @param refresh <code>true</code> if the cache should be refreshed.
|
|
|
1345 |
* @return the references.
|
|
|
1346 |
* @throws IOException if an error occurs.
|
|
|
1347 |
*/
|
|
|
1348 |
final List<ModuleReference> getAdminRequiredModules(final boolean refresh) throws IOException {
|
|
|
1349 |
final Preferences prefs = getRequiredIDsPrefs();
|
|
|
1350 |
if (refresh) {
|
|
|
1351 |
try {
|
|
|
1352 |
prefs.sync();
|
|
|
1353 |
} catch (BackingStoreException e) {
|
|
|
1354 |
// hide exception with a more common one
|
|
|
1355 |
throw new IOException("Couldn't sync preferences", e);
|
67 |
ilm |
1356 |
}
|
|
|
1357 |
}
|
80 |
ilm |
1358 |
final List<ModuleReference> res = getRefs(prefs);
|
|
|
1359 |
L.config("getAdminRequiredModules() found " + res);
|
|
|
1360 |
return res;
|
|
|
1361 |
}
|
|
|
1362 |
|
|
|
1363 |
private final boolean isAdminRequired(ModuleReference ref) {
|
|
|
1364 |
final long version = ref.getVersion().getMerged();
|
|
|
1365 |
assert version >= MIN_VERSION;
|
|
|
1366 |
return version == getRequiredIDsPrefs().getLong(ref.getID(), MIN_VERSION - 1);
|
|
|
1367 |
}
|
|
|
1368 |
|
|
|
1369 |
final void setAdminRequiredModules(final Set<ModuleReference> refs, final boolean required) throws BackingStoreException {
|
|
|
1370 |
final Set<ModuleReference> emptySet = Collections.<ModuleReference> emptySet();
|
|
|
1371 |
setAdminRequiredModules(required ? refs : emptySet, !required ? refs : emptySet);
|
|
|
1372 |
}
|
|
|
1373 |
|
|
|
1374 |
/**
|
|
|
1375 |
* Change which modules are required. This also {@link Preferences#sync()} the preferences if
|
|
|
1376 |
* they are modified.
|
|
|
1377 |
*
|
|
|
1378 |
* @param requiredRefs the modules required.
|
|
|
1379 |
* @param notRequiredRefs the modules not required.
|
|
|
1380 |
* @throws BackingStoreException if an error occurs.
|
|
|
1381 |
* @see #getAdminRequiredModules(boolean)
|
|
|
1382 |
*/
|
|
|
1383 |
final void setAdminRequiredModules(final Set<ModuleReference> requiredRefs, final Set<ModuleReference> notRequiredRefs) throws BackingStoreException {
|
|
|
1384 |
if (requiredRefs.size() + notRequiredRefs.size() == 0)
|
|
|
1385 |
return;
|
|
|
1386 |
if (!currentUserIsAdmin())
|
|
|
1387 |
throw new IllegalStateException("Not allowed to not require " + notRequiredRefs + " and to require " + requiredRefs);
|
|
|
1388 |
final Preferences prefs = getRequiredIDsPrefs();
|
|
|
1389 |
putRefs(prefs, requiredRefs);
|
|
|
1390 |
for (final ModuleReference ref : notRequiredRefs) {
|
|
|
1391 |
prefs.remove(ref.getID());
|
67 |
ilm |
1392 |
}
|
80 |
ilm |
1393 |
prefs.sync();
|
|
|
1394 |
}
|
67 |
ilm |
1395 |
|
80 |
ilm |
1396 |
public final void startRequiredModules() throws Exception {
|
|
|
1397 |
// use NO_CHANGE as installation should have been handled in init()
|
|
|
1398 |
startModules(getAdminRequiredModules(), NoChoicePredicate.NO_CHANGE, false);
|
27 |
ilm |
1399 |
}
|
|
|
1400 |
|
80 |
ilm |
1401 |
static private final List<ModuleReference> getRefs(final Preferences prefs) throws IOException {
|
|
|
1402 |
final String[] ids;
|
|
|
1403 |
try {
|
|
|
1404 |
ids = prefs.keys();
|
|
|
1405 |
} catch (BackingStoreException e) {
|
|
|
1406 |
// hide exception with a more common one
|
|
|
1407 |
throw new IOException("Couldn't access preferences", e);
|
|
|
1408 |
}
|
|
|
1409 |
final List<ModuleReference> refs = new ArrayList<ModuleReference>(ids.length);
|
|
|
1410 |
for (final String id : ids) {
|
|
|
1411 |
final long merged = prefs.getLong(id, MIN_VERSION - 1);
|
|
|
1412 |
refs.add(new ModuleReference(id, merged < MIN_VERSION ? null : new ModuleVersion(merged)));
|
|
|
1413 |
}
|
|
|
1414 |
return refs;
|
|
|
1415 |
}
|
|
|
1416 |
|
|
|
1417 |
static private final void putRefs(final Preferences prefs, final Collection<ModuleReference> refs) throws BackingStoreException {
|
|
|
1418 |
for (final ModuleReference ref : refs) {
|
|
|
1419 |
prefs.putLong(ref.getID(), ref.getVersion().getMerged());
|
|
|
1420 |
}
|
|
|
1421 |
prefs.flush();
|
|
|
1422 |
}
|
|
|
1423 |
|
61 |
ilm |
1424 |
/**
|
|
|
1425 |
* Start modules that were deemed persistent.
|
|
|
1426 |
*
|
|
|
1427 |
* @throws Exception if an error occurs.
|
|
|
1428 |
* @see #startModules(Collection, boolean)
|
|
|
1429 |
* @see #stopModule(String, boolean)
|
|
|
1430 |
*/
|
25 |
ilm |
1431 |
public final void startPreviouslyRunningModules() throws Exception {
|
80 |
ilm |
1432 |
final List<ModuleReference> ids = getRefs(getRunningIDsPrefs());
|
|
|
1433 |
L.config("startPreviouslyRunningModules() found " + ids);
|
|
|
1434 |
startModules(ids, NoChoicePredicate.ONLY_INSTALL_ARGUMENTS, false);
|
18 |
ilm |
1435 |
}
|
|
|
1436 |
|
|
|
1437 |
public final boolean startModule(final String id) throws Exception {
|
|
|
1438 |
return this.startModule(id, true);
|
|
|
1439 |
}
|
|
|
1440 |
|
|
|
1441 |
public final boolean startModule(final String id, final boolean persistent) throws Exception {
|
80 |
ilm |
1442 |
return this.startModule(new ModuleReference(id, null), persistent);
|
18 |
ilm |
1443 |
}
|
|
|
1444 |
|
80 |
ilm |
1445 |
public final boolean startModule(final ModuleReference id, final boolean persistent) throws Exception {
|
|
|
1446 |
return this.startModule(id, NoChoicePredicate.ONLY_INSTALL_ARGUMENTS, persistent);
|
|
|
1447 |
}
|
|
|
1448 |
|
|
|
1449 |
// return true if the module is now (or at least submitted to invokeLater()) started (even if
|
|
|
1450 |
// the module wasn't started by this method)
|
|
|
1451 |
public final boolean startModule(final ModuleReference id, final NoChoicePredicate noChoicePredicate, final boolean persistent) throws Exception {
|
|
|
1452 |
final Set<ModuleReference> notStarted = startModules(Collections.singleton(id), noChoicePredicate, persistent);
|
|
|
1453 |
final boolean res = notStarted.isEmpty();
|
|
|
1454 |
assert res == this.runningModules.containsKey(id.getID());
|
|
|
1455 |
return res;
|
|
|
1456 |
}
|
|
|
1457 |
|
61 |
ilm |
1458 |
/**
|
|
|
1459 |
* Start the passed modules. If this method is called outside of the EDT the modules will be
|
|
|
1460 |
* actually started using {@link SwingUtilities#invokeLater(Runnable)}, thus code that needs the
|
|
|
1461 |
* module to be actually started must also be called inside an invokeLater().
|
|
|
1462 |
*
|
|
|
1463 |
* @param ids which modules to start.
|
80 |
ilm |
1464 |
* @param noChoicePredicate which modules are allowed to be installed or removed.
|
61 |
ilm |
1465 |
* @param persistent <code>true</code> to start them the next time the application is launched,
|
|
|
1466 |
* see {@link #startPreviouslyRunningModules()}.
|
80 |
ilm |
1467 |
* @return the not started modules.
|
61 |
ilm |
1468 |
* @throws Exception if an error occurs.
|
|
|
1469 |
*/
|
80 |
ilm |
1470 |
public synchronized final Set<ModuleReference> startModules(final Collection<ModuleReference> ids, final NoChoicePredicate noChoicePredicate, final boolean persistent) throws Exception {
|
|
|
1471 |
// since we ask to start ids, only the not created are not started
|
|
|
1472 |
return this.createModules(ids, noChoicePredicate, ModuleState.STARTED, persistent).get1().getNotCreated();
|
|
|
1473 |
}
|
|
|
1474 |
|
|
|
1475 |
// ATTN versions are ignored (this.runningModules is used)
|
|
|
1476 |
synchronized Set<ModuleReference> setPersistentModules(final Collection<ModuleReference> ids) throws BackingStoreException {
|
|
|
1477 |
Map<String, ModuleVersion> modulesInstalled = null;
|
|
|
1478 |
final Set<ModuleReference> toSet = new HashSet<ModuleReference>();
|
|
|
1479 |
for (final ModuleReference ref : ids) {
|
|
|
1480 |
// use installedRef, since ids can contain null version
|
|
|
1481 |
final ModuleReference installedRef;
|
|
|
1482 |
|
|
|
1483 |
// first cheap in-memory check with this.runningModules
|
|
|
1484 |
final AbstractModule m = this.runningModules.get(ref.getID());
|
|
|
1485 |
if (m != null) {
|
|
|
1486 |
installedRef = m.getFactory().getReference();
|
|
|
1487 |
} else {
|
|
|
1488 |
// else check with local file system
|
|
|
1489 |
if (modulesInstalled == null)
|
|
|
1490 |
modulesInstalled = getModulesVersionInstalledLocally();
|
|
|
1491 |
installedRef = new ModuleReference(ref.getID(), modulesInstalled.get(ref.getID()));
|
18 |
ilm |
1492 |
}
|
80 |
ilm |
1493 |
if (installedRef.getVersion() != null) {
|
|
|
1494 |
toSet.add(installedRef);
|
|
|
1495 |
}
|
18 |
ilm |
1496 |
}
|
80 |
ilm |
1497 |
putRefs(getRunningIDsPrefs(), toSet);
|
|
|
1498 |
return toSet;
|
18 |
ilm |
1499 |
}
|
|
|
1500 |
|
80 |
ilm |
1501 |
static public enum InvalidRef {
|
|
|
1502 |
/**
|
|
|
1503 |
* The reference has no available factory.
|
|
|
1504 |
*/
|
|
|
1505 |
NO_FACTORY,
|
|
|
1506 |
/**
|
|
|
1507 |
* The reference conflicts with another reference in the same call.
|
|
|
1508 |
*/
|
|
|
1509 |
SELF_CONFLICT,
|
|
|
1510 |
/**
|
|
|
1511 |
* The reference cannot be installed (e.g. missing dependency, cycle...).
|
|
|
1512 |
*/
|
|
|
1513 |
NO_SOLUTION
|
18 |
ilm |
1514 |
}
|
|
|
1515 |
|
80 |
ilm |
1516 |
// the pool to use
|
|
|
1517 |
// refs that could be installed
|
|
|
1518 |
// refs that cannot be installed
|
|
|
1519 |
// => instances of <code>refs</code> not returned are duplicates or references without version
|
|
|
1520 |
private final Tuple3<FactoriesByID, List<ModuleReference>, SetMap<InvalidRef, ModuleReference>> resolveRefs(final Collection<ModuleReference> refs) {
|
|
|
1521 |
// remove duplicates
|
|
|
1522 |
final Set<ModuleReference> refsSet = new HashSet<ModuleReference>(refs);
|
|
|
1523 |
// only keep references without version if no other specifies a version
|
|
|
1524 |
final Set<String> nonNullVersions = new HashSet<String>();
|
|
|
1525 |
for (final ModuleReference ref : refsSet) {
|
|
|
1526 |
if (ref.getVersion() != null)
|
|
|
1527 |
nonNullVersions.add(ref.getID());
|
|
|
1528 |
}
|
|
|
1529 |
// refs with only one factory (either specifying one version or with only one version
|
|
|
1530 |
// available)
|
|
|
1531 |
final List<ModuleFactory> factories = new ArrayList<ModuleFactory>();
|
|
|
1532 |
final List<ModuleReference> atLeast1 = new ArrayList<ModuleReference>();
|
|
|
1533 |
final Iterator<ModuleReference> iter = refsSet.iterator();
|
|
|
1534 |
while (iter.hasNext()) {
|
|
|
1535 |
final ModuleReference ref = iter.next();
|
|
|
1536 |
if (ref.getVersion() == null && nonNullVersions.contains(ref.getID())) {
|
|
|
1537 |
// only use the reference that specifies a version
|
|
|
1538 |
iter.remove();
|
|
|
1539 |
} else {
|
|
|
1540 |
final List<ModuleFactory> factoriesForRef = this.factories.getFactories(ref);
|
|
|
1541 |
final int size = factoriesForRef.size();
|
|
|
1542 |
if (size > 0) {
|
|
|
1543 |
iter.remove();
|
|
|
1544 |
atLeast1.add(ref);
|
|
|
1545 |
if (size == 1) {
|
|
|
1546 |
factories.add(factoriesForRef.get(0));
|
|
|
1547 |
}
|
67 |
ilm |
1548 |
}
|
18 |
ilm |
1549 |
}
|
|
|
1550 |
}
|
80 |
ilm |
1551 |
final SetMap<InvalidRef, ModuleReference> invalidRefs = new SetMap<InvalidRef, ModuleReference>(Mode.NULL_FORBIDDEN);
|
|
|
1552 |
invalidRefs.putCollection(InvalidRef.NO_FACTORY, refsSet);
|
|
|
1553 |
|
|
|
1554 |
final FactoriesByID fByID = this.copyFactories();
|
|
|
1555 |
// conflicts with requested references
|
|
|
1556 |
final Set<ModuleFactory> conflicts = fByID.getConflicts(factories);
|
|
|
1557 |
final Collection<ModuleFactory> selfConflicts = CollectionUtils.intersection(factories, conflicts);
|
|
|
1558 |
for (final ModuleFactory f : selfConflicts) {
|
|
|
1559 |
invalidRefs.add(InvalidRef.SELF_CONFLICT, f.getReference());
|
|
|
1560 |
// don't bother trying
|
|
|
1561 |
atLeast1.remove(f.getReference());
|
|
|
1562 |
}
|
|
|
1563 |
fByID.removeAll(conflicts);
|
|
|
1564 |
// make sure that the pool is coherent with the solving graph
|
|
|
1565 |
fByID.addAll(this.dependencyGraph.vertexSet());
|
|
|
1566 |
return Tuple3.create(fByID, atLeast1, invalidRefs);
|
|
|
1567 |
}
|
|
|
1568 |
|
|
|
1569 |
/**
|
|
|
1570 |
* Allow to create modules without user interaction.
|
|
|
1571 |
*
|
|
|
1572 |
* @author Sylvain
|
|
|
1573 |
*/
|
|
|
1574 |
static enum NoChoicePredicate {
|
|
|
1575 |
/** No install, no uninstall */
|
|
|
1576 |
NO_CHANGE,
|
|
|
1577 |
/**
|
|
|
1578 |
* No uninstall, only install passed modules.
|
|
|
1579 |
*/
|
|
|
1580 |
ONLY_INSTALL_ARGUMENTS,
|
|
|
1581 |
/**
|
|
|
1582 |
* No uninstall, only install passed modules and their dependencies.
|
|
|
1583 |
*/
|
|
|
1584 |
ONLY_INSTALL
|
|
|
1585 |
}
|
|
|
1586 |
|
|
|
1587 |
synchronized private final DepSolver createSolver(final int maxCount, final NoChoicePredicate s, final Collection<ModuleReference> ids) throws Exception {
|
|
|
1588 |
final InstallationState installState = new InstallationState(this);
|
|
|
1589 |
final DepSolver depSolver = new DepSolver().setMaxSuccess(maxCount);
|
|
|
1590 |
depSolver.setResultFactory(new Factory() {
|
|
|
1591 |
@Override
|
|
|
1592 |
public DepSolverResult create(DepSolverResult parent, int tryCount, String error, DepSolverGraph graph) {
|
|
|
1593 |
final DepSolverResultMM res = new DepSolverResultMM((DepSolverResultMM) parent, tryCount, error, graph);
|
|
|
1594 |
res.init(ModuleManager.this, installState, s, ids);
|
|
|
1595 |
return res;
|
67 |
ilm |
1596 |
}
|
80 |
ilm |
1597 |
});
|
144 |
ilm |
1598 |
depSolver.setResultFilter(DepSolverResultMM.VALID_PRED);
|
80 |
ilm |
1599 |
return depSolver;
|
|
|
1600 |
}
|
|
|
1601 |
|
|
|
1602 |
synchronized final Tuple2<Solutions, ModulesStateChangeResult> createModules(final Collection<ModuleReference> ids, final NoChoicePredicate s, final ModuleState targetState) throws Exception {
|
|
|
1603 |
return this.createModules(ids, s, targetState, checkPersistentNeeded(targetState));
|
|
|
1604 |
}
|
|
|
1605 |
|
|
|
1606 |
// allow to not pass unneeded argument
|
|
|
1607 |
private boolean checkPersistentNeeded(final ModuleState targetState) {
|
|
|
1608 |
if (targetState.compareTo(ModuleState.STARTED) >= 0)
|
|
|
1609 |
throw new IllegalArgumentException("For STARTED the persistent parameter must be supplied");
|
|
|
1610 |
return false;
|
|
|
1611 |
}
|
|
|
1612 |
|
|
|
1613 |
// not public since it returns instance of AbstractModules
|
|
|
1614 |
synchronized final Tuple2<Solutions, ModulesStateChangeResult> createModules(final Collection<ModuleReference> ids, final NoChoicePredicate s, final ModuleState targetState,
|
|
|
1615 |
final boolean startPersistent) throws Exception {
|
|
|
1616 |
// Don't uninstall automatically, use getSolutions() then applyChange()
|
|
|
1617 |
if (s == null)
|
|
|
1618 |
throw new NullPointerException();
|
|
|
1619 |
if (ids.size() == 0 || targetState == ModuleState.NOT_CREATED)
|
|
|
1620 |
return Tuple2.create(Solutions.EMPTY, ModulesStateChangeResult.empty());
|
|
|
1621 |
|
|
|
1622 |
final DepSolver depSolver = createSolver(1, s, ids);
|
|
|
1623 |
final Solutions solutions = getSolutions(depSolver, ids);
|
|
|
1624 |
final SetMap<InvalidRef, ModuleReference> cannotCreate = solutions.getNotSolvedReferences();
|
|
|
1625 |
final ModulesStateChangeResult changeRes;
|
|
|
1626 |
// don't partially install
|
|
|
1627 |
if (cannotCreate != null && !cannotCreate.isEmpty()) {
|
|
|
1628 |
changeRes = ModulesStateChangeResult.noneCreated(new HashSet<ModuleReference>(ids));
|
|
|
1629 |
} else {
|
|
|
1630 |
// at least one solution otherwise cannotCreate wouldn't be empty
|
|
|
1631 |
changeRes = this.applyChange((DepSolverResultMM) solutions.getSolutions().get(0), targetState, startPersistent);
|
61 |
ilm |
1632 |
}
|
80 |
ilm |
1633 |
return Tuple2.create(solutions, changeRes);
|
|
|
1634 |
}
|
18 |
ilm |
1635 |
|
80 |
ilm |
1636 |
synchronized final Solutions getSolutions(final Collection<ModuleReference> ids, final int maxCount) throws Exception {
|
|
|
1637 |
return this.getSolutions(createSolver(maxCount, null, ids), ids);
|
|
|
1638 |
}
|
67 |
ilm |
1639 |
|
80 |
ilm |
1640 |
synchronized private final Solutions getSolutions(final DepSolver depSolver, final Collection<ModuleReference> ids) throws Exception {
|
|
|
1641 |
if (ids.size() == 0)
|
|
|
1642 |
return Solutions.EMPTY;
|
67 |
ilm |
1643 |
|
80 |
ilm |
1644 |
final Tuple3<FactoriesByID, List<ModuleReference>, SetMap<InvalidRef, ModuleReference>> resolvedRefs = resolveRefs(ids);
|
|
|
1645 |
final FactoriesByID pool = resolvedRefs.get0();
|
|
|
1646 |
final List<ModuleReference> atLeast1 = resolvedRefs.get1();
|
|
|
1647 |
final SetMap<InvalidRef, ModuleReference> invalidRefs = resolvedRefs.get2();
|
61 |
ilm |
1648 |
|
80 |
ilm |
1649 |
final List<DepSolverResult> solutions;
|
|
|
1650 |
if (atLeast1.isEmpty()) {
|
|
|
1651 |
// we were passed non empty references to install but no candidates remain. If we passed
|
|
|
1652 |
// an empty list to DepSolver it will immediately return successfully.
|
|
|
1653 |
solutions = Collections.emptyList();
|
|
|
1654 |
} else {
|
|
|
1655 |
solutions = depSolver.solve(pool, this.dependencyGraph, atLeast1);
|
|
|
1656 |
}
|
|
|
1657 |
if (solutions.size() == 0) {
|
|
|
1658 |
invalidRefs.putCollection(InvalidRef.NO_SOLUTION, atLeast1);
|
|
|
1659 |
}
|
|
|
1660 |
invalidRefs.removeAllEmptyCollections();
|
|
|
1661 |
return new Solutions(invalidRefs, solutions.size() == 0 ? Collections.<ModuleReference> emptyList() : atLeast1, solutions);
|
|
|
1662 |
}
|
61 |
ilm |
1663 |
|
80 |
ilm |
1664 |
synchronized final ModulesStateChangeResult applyChange(final ModulesStateChange change, final ModuleState targetState) throws Exception {
|
|
|
1665 |
return applyChange(change, targetState, checkPersistentNeeded(targetState));
|
|
|
1666 |
}
|
|
|
1667 |
|
|
|
1668 |
// not public since it returns instances of AbstractModule
|
|
|
1669 |
// @param targetState target state for modules in graph
|
|
|
1670 |
// @param startPersistent only used if <code>targetState</code> is STARTED
|
|
|
1671 |
synchronized final ModulesStateChangeResult applyChange(final ModulesStateChange change, final ModuleState targetState, final boolean startPersistent) throws Exception {
|
|
|
1672 |
if (change == null || change.getError() != null) {
|
|
|
1673 |
return null;
|
|
|
1674 |
} else if (!new InstallationState(this).equals(change.getInstallState())) {
|
|
|
1675 |
throw new IllegalStateException("Installation state has changed since getSolutions()");
|
|
|
1676 |
}
|
|
|
1677 |
|
151 |
ilm |
1678 |
// call it before stopping/uninstalling
|
|
|
1679 |
final boolean exit = this.isExitAllowed() && this.needExit(change);
|
|
|
1680 |
|
156 |
ilm |
1681 |
final DepSolverGraph graph = change.getGraph();
|
80 |
ilm |
1682 |
final Set<ModuleReference> toRemove = change.getReferencesToRemove();
|
|
|
1683 |
final Set<ModuleReference> removed;
|
|
|
1684 |
if (toRemove.size() > 0) {
|
149 |
ilm |
1685 |
final Set<String> idsToInstall = change.getIDsToInstall();
|
|
|
1686 |
|
80 |
ilm |
1687 |
removed = new HashSet<ModuleReference>();
|
|
|
1688 |
for (final ModuleReference ref : toRemove) {
|
149 |
ilm |
1689 |
// don't uninstall modules to upgrade but since this loop might uninstall modules
|
|
|
1690 |
// needed by ref, at least stop it like uninstallUnsafe() does
|
|
|
1691 |
if (idsToInstall.contains(ref.getID()))
|
|
|
1692 |
this.stopModule(ref.getID(), false);
|
156 |
ilm |
1693 |
else if (this.uninstallUnsafe(ref, !change.forceRemove(), change.getInstallState()))
|
80 |
ilm |
1694 |
removed.add(ref);
|
61 |
ilm |
1695 |
}
|
80 |
ilm |
1696 |
} else {
|
|
|
1697 |
removed = Collections.emptySet();
|
|
|
1698 |
}
|
|
|
1699 |
|
|
|
1700 |
// MAYBE compare states with targetState to avoid going further (e.g ids are all started)
|
|
|
1701 |
|
151 |
ilm |
1702 |
if (exit) {
|
80 |
ilm |
1703 |
// restart to make sure the uninstalled modules are really gone from the memory and
|
|
|
1704 |
// none of its effects present. We could check that the class loader for the module
|
|
|
1705 |
// is garbage collected, but
|
|
|
1706 |
// 1. this cannot work if the module is in the class path
|
|
|
1707 |
// 2. an ill-behaved modules might have modified a static value
|
149 |
ilm |
1708 |
assert noDisplayableFrame() : "A change needs to exit but there still a displayable frame : " + change;
|
80 |
ilm |
1709 |
final Set<ModuleReference> toInstall = change.getReferencesToInstall();
|
|
|
1710 |
// don't use only getReferencesToInstall() as even if no modules need installing, their
|
|
|
1711 |
// state might need to change (e.g. start)
|
|
|
1712 |
if (toInstall.size() > 0 || (targetState.compareTo(ModuleState.INSTALLED) > 0 && change.getUserReferencesToInstall().size() > 0)) {
|
|
|
1713 |
// record current time and actions
|
|
|
1714 |
final File f = getToInstallFile();
|
156 |
ilm |
1715 |
try (final XMLEncoder xmlEncoder = new XMLEncoder(new FileOutputStream(f))) {
|
80 |
ilm |
1716 |
xmlEncoder.setExceptionListener(XMLCodecUtils.EXCEPTION_LISTENER);
|
|
|
1717 |
xmlEncoder.setPersistenceDelegate(ModuleVersion.class, ModuleVersion.PERSIST_DELEGATE);
|
|
|
1718 |
xmlEncoder.setPersistenceDelegate(ModuleReference.class, ModuleReference.PERSIST_DELEGATE);
|
|
|
1719 |
xmlEncoder.writeObject(TO_INSTALL_VERSION);
|
|
|
1720 |
xmlEncoder.writeObject(new Date());
|
|
|
1721 |
xmlEncoder.writeObject(toInstall);
|
|
|
1722 |
xmlEncoder.writeObject(change.getUserReferencesToInstall());
|
|
|
1723 |
xmlEncoder.writeObject(targetState);
|
|
|
1724 |
xmlEncoder.writeObject(startPersistent);
|
|
|
1725 |
} catch (Exception e) {
|
|
|
1726 |
// try to delete invalid file before throwing exception
|
156 |
ilm |
1727 |
// "any catch or finally block is run after the resources have been closed."
|
80 |
ilm |
1728 |
f.delete();
|
|
|
1729 |
throw e;
|
73 |
ilm |
1730 |
}
|
80 |
ilm |
1731 |
}
|
156 |
ilm |
1732 |
return new ModulesStateChangeResult(removed, change.getReferencesToInstall(), graph, Collections.emptyMap());
|
18 |
ilm |
1733 |
}
|
|
|
1734 |
|
80 |
ilm |
1735 |
// don't use getReferencesToInstall() as even if no modules need installing, their state
|
|
|
1736 |
// might need to change (e.g. start)
|
|
|
1737 |
if (targetState.compareTo(ModuleState.CREATED) < 0)
|
|
|
1738 |
return ModulesStateChangeResult.onlyRemoved(removed);
|
|
|
1739 |
|
|
|
1740 |
if (graph == null)
|
156 |
ilm |
1741 |
throw new IllegalArgumentException("target state is " + targetState + " but no graph was provided by " + change);
|
80 |
ilm |
1742 |
|
|
|
1743 |
// modules created by this method
|
|
|
1744 |
final Map<ModuleReference, AbstractModule> modules = new LinkedHashMap<ModuleReference, AbstractModule>(graph.getFactories().size());
|
|
|
1745 |
// MAYBE try to continue even if some modules couldn't be created
|
|
|
1746 |
final Set<ModuleReference> cannotCreate = Collections.emptySet();
|
|
|
1747 |
|
|
|
1748 |
final List<AbstractModule> toStart = new ArrayList<AbstractModule>();
|
|
|
1749 |
|
|
|
1750 |
for (final ModuleFactory useableFactory : graph.flatten()) {
|
|
|
1751 |
final String id = useableFactory.getID();
|
|
|
1752 |
// already created
|
|
|
1753 |
if (!this.dependencyGraph.containsVertex(useableFactory)) {
|
|
|
1754 |
final Map<Object, ModuleFactory> dependenciesFactory = graph.getDependencies(useableFactory);
|
|
|
1755 |
final Map<Object, AbstractModule> dependenciesModule = new HashMap<Object, AbstractModule>(dependenciesFactory.size());
|
|
|
1756 |
for (final Entry<Object, ModuleFactory> e : dependenciesFactory.entrySet()) {
|
|
|
1757 |
final AbstractModule module = this.createdModules.get(e.getValue());
|
|
|
1758 |
assert module != null;
|
|
|
1759 |
dependenciesModule.put(e.getKey(), module);
|
|
|
1760 |
}
|
156 |
ilm |
1761 |
final AbstractModule createdModule = useableFactory.createModule(this.getLocalDataDirectory(id), Collections.unmodifiableMap(dependenciesModule));
|
80 |
ilm |
1762 |
modules.put(useableFactory.getReference(), createdModule);
|
|
|
1763 |
this.createdModules.put(useableFactory, createdModule);
|
|
|
1764 |
|
|
|
1765 |
// update graph
|
|
|
1766 |
final boolean added = this.dependencyGraph.addVertex(useableFactory);
|
|
|
1767 |
assert added : "Module was already in graph : " + useableFactory;
|
|
|
1768 |
for (final Entry<Object, ModuleFactory> e : dependenciesFactory.entrySet()) {
|
|
|
1769 |
this.dependencyGraph.addEdge(useableFactory, e.getKey(), e.getValue());
|
|
|
1770 |
}
|
|
|
1771 |
}
|
|
|
1772 |
// even if the module was created in a previous invocation, it might not have been
|
|
|
1773 |
// started then
|
|
|
1774 |
if (!this.runningModules.containsKey(id))
|
|
|
1775 |
toStart.add(this.createdModules.get(useableFactory));
|
|
|
1776 |
}
|
|
|
1777 |
|
|
|
1778 |
// don't test toStart emptiness as even if all modules were started, they might need to be
|
|
|
1779 |
// made persistent
|
|
|
1780 |
if (targetState.compareTo(ModuleState.INSTALLED) >= 0) {
|
149 |
ilm |
1781 |
// register each module just after install, so that the next module can use its elements
|
|
|
1782 |
// in its install
|
|
|
1783 |
for (final AbstractModule module : toStart) {
|
80 |
ilm |
1784 |
installAndRegister(module, graph);
|
149 |
ilm |
1785 |
}
|
80 |
ilm |
1786 |
|
|
|
1787 |
if (targetState == ModuleState.STARTED) {
|
|
|
1788 |
start(toStart);
|
|
|
1789 |
if (startPersistent)
|
|
|
1790 |
// only mark persistent passed modules (not their dependencies)
|
|
|
1791 |
this.setPersistentModules(change.getUserReferencesToInstall());
|
|
|
1792 |
}
|
|
|
1793 |
}
|
|
|
1794 |
|
|
|
1795 |
// ATTN modules indexed by resolved references, not the ones passed
|
|
|
1796 |
return new ModulesStateChangeResult(removed, cannotCreate, graph, modules);
|
18 |
ilm |
1797 |
}
|
|
|
1798 |
|
80 |
ilm |
1799 |
synchronized final void startFactories(final List<ModuleFactory> toStart) throws Exception {
|
|
|
1800 |
final List<AbstractModule> modules = new ArrayList<AbstractModule>(toStart.size());
|
|
|
1801 |
for (final ModuleFactory f : toStart) {
|
|
|
1802 |
final AbstractModule m = this.createdModules.get(f);
|
|
|
1803 |
if (m == null)
|
|
|
1804 |
throw new IllegalStateException("Not created : " + f);
|
|
|
1805 |
else if (!this.isModuleRunning(f.getID()))
|
|
|
1806 |
modules.add(m);
|
|
|
1807 |
}
|
|
|
1808 |
this.start(modules);
|
|
|
1809 |
}
|
67 |
ilm |
1810 |
|
80 |
ilm |
1811 |
synchronized private final void start(final List<AbstractModule> toStart) throws Exception {
|
|
|
1812 |
if (toStart.size() == 0)
|
|
|
1813 |
return;
|
|
|
1814 |
// check install state before starting
|
|
|
1815 |
final Set<ModuleReference> registeredModules = this.getRegisteredModules();
|
|
|
1816 |
for (final AbstractModule m : toStart) {
|
|
|
1817 |
final ModuleReference ref = m.getFactory().getReference();
|
|
|
1818 |
if (!registeredModules.contains(ref))
|
|
|
1819 |
throw new IllegalStateException("Not installed and registered : " + ref);
|
|
|
1820 |
}
|
|
|
1821 |
// a module can always start if installed
|
|
|
1822 |
|
|
|
1823 |
final FutureTask<MenuAndActions> menuAndActions = new FutureTask<MenuAndActions>(new Callable<MenuAndActions>() {
|
|
|
1824 |
@Override
|
|
|
1825 |
public MenuAndActions call() throws Exception {
|
|
|
1826 |
return MenuManager.getInstance().copyMenuAndActions();
|
|
|
1827 |
}
|
|
|
1828 |
});
|
|
|
1829 |
SwingThreadUtils.invoke(menuAndActions);
|
|
|
1830 |
for (final AbstractModule module : toStart) {
|
|
|
1831 |
final ModuleFactory f = module.getFactory();
|
|
|
1832 |
final String id = f.getID();
|
67 |
ilm |
1833 |
try {
|
80 |
ilm |
1834 |
// do the request here instead of in the EDT in setupComponents()
|
|
|
1835 |
assert !this.runningModules.containsKey(id) : "Doing a request for nothing";
|
|
|
1836 |
final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
|
|
|
1837 |
// execute right away if possible, allowing the caller to handle any exceptions
|
|
|
1838 |
if (SwingUtilities.isEventDispatchThread()) {
|
|
|
1839 |
startModule(module, createdItems, menuAndActions.get());
|
|
|
1840 |
} else {
|
|
|
1841 |
// keep the for outside to avoid halting the EDT too long
|
|
|
1842 |
SwingUtilities.invokeLater(new Runnable() {
|
|
|
1843 |
@Override
|
|
|
1844 |
public void run() {
|
|
|
1845 |
try {
|
|
|
1846 |
startModule(module, createdItems, menuAndActions.get());
|
|
|
1847 |
} catch (Exception e) {
|
|
|
1848 |
ExceptionHandler.handle(MainFrame.getInstance(), "Unable to start " + f, e);
|
|
|
1849 |
}
|
|
|
1850 |
}
|
|
|
1851 |
});
|
|
|
1852 |
}
|
|
|
1853 |
} catch (Exception e) {
|
|
|
1854 |
throw new Exception("Couldn't start module " + module, e);
|
|
|
1855 |
}
|
|
|
1856 |
|
|
|
1857 |
this.runningModules.put(id, module);
|
|
|
1858 |
}
|
|
|
1859 |
SwingThreadUtils.invoke(new Runnable() {
|
|
|
1860 |
@Override
|
|
|
1861 |
public void run() {
|
|
|
1862 |
try {
|
|
|
1863 |
MenuManager.getInstance().setMenuAndActions(menuAndActions.get());
|
|
|
1864 |
} catch (Exception e) {
|
|
|
1865 |
ExceptionHandler.handle(MainFrame.getInstance(), "Unable to update menu", e);
|
|
|
1866 |
}
|
|
|
1867 |
}
|
|
|
1868 |
});
|
|
|
1869 |
}
|
|
|
1870 |
|
|
|
1871 |
private final Set<SQLTable> loadTranslations(final SQLFieldTranslator trns, final AbstractModule module, final String mdVariant) throws IOException {
|
177 |
ilm |
1872 |
final Locale locale = getConf().getLocale();
|
80 |
ilm |
1873 |
final Control cntrl = TranslationManager.getControl();
|
|
|
1874 |
final String baseName = "labels";
|
|
|
1875 |
|
|
|
1876 |
final Set<SQLTable> res = new HashSet<SQLTable>();
|
|
|
1877 |
boolean found = false;
|
|
|
1878 |
for (Locale targetLocale = locale; targetLocale != null && !found; targetLocale = cntrl.getFallbackLocale(baseName, targetLocale)) {
|
|
|
1879 |
final List<Locale> langs = cntrl.getCandidateLocales(baseName, targetLocale);
|
|
|
1880 |
// SQLFieldTranslator overwrite, so we need to load from general to specific
|
|
|
1881 |
final ListIterator<Locale> listIterator = CollectionUtils.getListIterator(langs, true);
|
|
|
1882 |
while (listIterator.hasNext()) {
|
|
|
1883 |
final Locale lang = listIterator.next();
|
156 |
ilm |
1884 |
final SQLElementNamesFromXML elemNames = new SQLElementNamesFromXML(lang);
|
|
|
1885 |
final String bundleName = cntrl.toBundleName(baseName, lang);
|
|
|
1886 |
final String resourceName = cntrl.toResourceName(bundleName, "xml");
|
81 |
ilm |
1887 |
final InputStream ins = module.getClass().getResourceAsStream(resourceName);
|
80 |
ilm |
1888 |
// do not force to have one mapping for each locale
|
|
|
1889 |
if (ins != null) {
|
83 |
ilm |
1890 |
L.config("module " + module.getName() + " loading translation from " + resourceName);
|
80 |
ilm |
1891 |
final Set<SQLTable> loadedTables;
|
67 |
ilm |
1892 |
try {
|
156 |
ilm |
1893 |
loadedTables = trns.load(getRoot(), mdVariant, ins, elemNames).get0();
|
67 |
ilm |
1894 |
} finally {
|
80 |
ilm |
1895 |
ins.close();
|
67 |
ilm |
1896 |
}
|
80 |
ilm |
1897 |
if (loadedTables.size() > 0) {
|
|
|
1898 |
res.addAll(loadedTables);
|
|
|
1899 |
found |= true;
|
|
|
1900 |
}
|
156 |
ilm |
1901 |
|
|
|
1902 |
// As in PropsConfiguration.loadTranslations(), perhaps load the class at
|
|
|
1903 |
// module.getClass().getPackage().getName() + '.' + bundleName
|
|
|
1904 |
// to allow more flexibility. Perhaps pass a ModuleSQLFieldTranslator to
|
|
|
1905 |
// restrict what a module can do (and have SQLElementNamesFromXML, mdVariant).
|
19 |
ilm |
1906 |
}
|
18 |
ilm |
1907 |
}
|
|
|
1908 |
}
|
80 |
ilm |
1909 |
return res;
|
18 |
ilm |
1910 |
}
|
|
|
1911 |
|
80 |
ilm |
1912 |
private final void installAndRegister(final AbstractModule module, DepSolverGraph graph) throws Exception {
|
|
|
1913 |
assert Thread.holdsLock(this);
|
|
|
1914 |
assert !isModuleRunning(module.getFactory().getID());
|
151 |
ilm |
1915 |
// Snapshot now to allow install() to register and use its own elements
|
|
|
1916 |
// Also needed for checks, since install() can do arbitrary changes to the directory
|
|
|
1917 |
final Map<SQLTable, SQLElement> beforeElements = new HashMap<SQLTable, SQLElement>(getDirectory().getElementsMap());
|
80 |
ilm |
1918 |
try {
|
|
|
1919 |
install(module, graph);
|
|
|
1920 |
} catch (Exception e) {
|
|
|
1921 |
throw new Exception("Couldn't install module " + module, e);
|
|
|
1922 |
}
|
|
|
1923 |
try {
|
151 |
ilm |
1924 |
this.registerSQLElements(module, beforeElements);
|
80 |
ilm |
1925 |
} catch (Exception e) {
|
|
|
1926 |
throw new Exception("Couldn't register module " + module, e);
|
|
|
1927 |
}
|
|
|
1928 |
}
|
|
|
1929 |
|
|
|
1930 |
private final void startModule(final AbstractModule module, final Tuple2<Set<String>, Set<SQLName>> createdItems, final MenuAndActions menuAndActions) throws Exception {
|
18 |
ilm |
1931 |
assert SwingUtilities.isEventDispatchThread();
|
80 |
ilm |
1932 |
this.setupComponents(module, createdItems, menuAndActions);
|
61 |
ilm |
1933 |
module.start();
|
|
|
1934 |
}
|
|
|
1935 |
|
73 |
ilm |
1936 |
private final void setupMenu(final AbstractModule module, final MenuAndActions menuAndActions) {
|
|
|
1937 |
module.setupMenu(new MenuContext(menuAndActions, module.getFactory().getID(), getDirectory(), getRoot()));
|
|
|
1938 |
}
|
|
|
1939 |
|
61 |
ilm |
1940 |
public synchronized final boolean isModuleRunning(final String id) {
|
18 |
ilm |
1941 |
return this.runningModules.containsKey(id);
|
|
|
1942 |
}
|
|
|
1943 |
|
61 |
ilm |
1944 |
/**
|
|
|
1945 |
* The modules that are currently running. NOTE : if {@link #startModules(Collection, boolean)}
|
|
|
1946 |
* or {@link #stopModule(String, boolean)} wasn't called from the EDT the modules will only be
|
|
|
1947 |
* actually started/stopped when the EDT executes the invokeLater(). In other words a module can
|
|
|
1948 |
* be in the result but not yet on screen, or module can no longer be in the result but still on
|
|
|
1949 |
* screen.
|
|
|
1950 |
*
|
|
|
1951 |
* @return the started modules.
|
|
|
1952 |
*/
|
|
|
1953 |
public synchronized final Map<String, AbstractModule> getRunningModules() {
|
|
|
1954 |
return new HashMap<String, AbstractModule>(this.runningModules);
|
18 |
ilm |
1955 |
}
|
|
|
1956 |
|
80 |
ilm |
1957 |
/**
|
|
|
1958 |
* The running modules depending on the passed one. E.g. if it isn't running returns an empty
|
|
|
1959 |
* list.
|
|
|
1960 |
*
|
|
|
1961 |
* @param id a module.
|
|
|
1962 |
* @return the running modules needing <code>id</code> (including itself), in stop order (i.e.
|
|
|
1963 |
* the first item isn't depended on).
|
|
|
1964 |
*/
|
|
|
1965 |
public synchronized final List<ModuleReference> getRunningDependentModulesRecursively(final String id) {
|
18 |
ilm |
1966 |
if (!this.isModuleRunning(id))
|
80 |
ilm |
1967 |
return Collections.emptyList();
|
18 |
ilm |
1968 |
|
|
|
1969 |
final ModuleFactory f = this.runningModules.get(id).getFactory();
|
80 |
ilm |
1970 |
return getRunningDependentModulesRecursively(f.getReference(), new LinkedList<ModuleReference>());
|
|
|
1971 |
}
|
|
|
1972 |
|
|
|
1973 |
private synchronized final List<ModuleReference> getRunningDependentModulesRecursively(final ModuleReference ref, final List<ModuleReference> res) {
|
|
|
1974 |
// can happen if a module depends on two others and they share a dependency, e.g.
|
|
|
1975 |
// __ B
|
|
|
1976 |
// A < > D
|
|
|
1977 |
// __ C
|
|
|
1978 |
if (!res.contains(ref) && this.isModuleRunning(ref.getID())) {
|
|
|
1979 |
final ModuleFactory f = this.runningModules.get(ref.getID()).getFactory();
|
|
|
1980 |
// the graph has no cycle, so we don't need to protected against infinite loop
|
|
|
1981 |
final Set<ModuleReference> deps = new TreeSet<ModuleReference>(ModuleReference.COMP_ID_ASC_VERSION_DESC);
|
|
|
1982 |
for (final DirectedEdge<ModuleFactory> e : this.dependencyGraph.incomingEdgesOf(f)) {
|
|
|
1983 |
deps.add(e.getSource().getReference());
|
|
|
1984 |
}
|
|
|
1985 |
for (final ModuleReference dep : deps) {
|
|
|
1986 |
this.getRunningDependentModulesRecursively(dep, res);
|
|
|
1987 |
}
|
|
|
1988 |
res.add(f.getReference());
|
18 |
ilm |
1989 |
}
|
80 |
ilm |
1990 |
return res;
|
18 |
ilm |
1991 |
}
|
|
|
1992 |
|
156 |
ilm |
1993 |
public final Set<ModuleReference> stopAllModules() {
|
|
|
1994 |
// this method is not synchronized, so don't just return getRunningModules()
|
|
|
1995 |
final Set<ModuleReference> res = new HashSet<>();
|
|
|
1996 |
for (final String id : this.getRunningModules().keySet()) {
|
|
|
1997 |
res.addAll(this.stopModuleRecursively(id));
|
|
|
1998 |
}
|
|
|
1999 |
return res;
|
|
|
2000 |
}
|
|
|
2001 |
|
|
|
2002 |
public synchronized final List<ModuleReference> stopModuleRecursively(final String id) {
|
|
|
2003 |
final List<ModuleReference> res = getRunningDependentModulesRecursively(id);
|
|
|
2004 |
for (final ModuleReference ref : res) {
|
80 |
ilm |
2005 |
this.stopModule(ref.getID());
|
|
|
2006 |
}
|
156 |
ilm |
2007 |
return res;
|
80 |
ilm |
2008 |
}
|
|
|
2009 |
|
156 |
ilm |
2010 |
public final boolean stopModule(final String id) {
|
|
|
2011 |
return this.stopModule(id, true);
|
18 |
ilm |
2012 |
}
|
|
|
2013 |
|
80 |
ilm |
2014 |
// TODO pass ModuleReference instead of ID (need to change this.runningModules)
|
156 |
ilm |
2015 |
public synchronized final boolean stopModule(final String id, final boolean persistent) {
|
18 |
ilm |
2016 |
if (!this.isModuleRunning(id))
|
156 |
ilm |
2017 |
return false;
|
18 |
ilm |
2018 |
|
|
|
2019 |
final ModuleFactory f = this.runningModules.get(id).getFactory();
|
80 |
ilm |
2020 |
if (this.isAdminRequired(f.getReference()) && !currentUserIsAdmin())
|
|
|
2021 |
throw new IllegalStateException("Not allowed to stop a module required by the administrator " + f);
|
|
|
2022 |
final Set<DepLink> deps = this.dependencyGraph.incomingEdgesOf(f);
|
|
|
2023 |
for (final DepLink l : deps) {
|
|
|
2024 |
if (this.isModuleRunning(l.getSource().getID()))
|
|
|
2025 |
throw new IllegalArgumentException("Some dependents still running : " + deps);
|
|
|
2026 |
}
|
18 |
ilm |
2027 |
final AbstractModule m = this.runningModules.remove(id);
|
61 |
ilm |
2028 |
try {
|
|
|
2029 |
// execute right away if possible, allowing the caller to handle any exceptions
|
|
|
2030 |
if (SwingUtilities.isEventDispatchThread()) {
|
|
|
2031 |
stopModule(m);
|
|
|
2032 |
} else {
|
|
|
2033 |
SwingUtilities.invokeLater(new Runnable() {
|
|
|
2034 |
@Override
|
|
|
2035 |
public void run() {
|
|
|
2036 |
try {
|
|
|
2037 |
stopModule(m);
|
|
|
2038 |
} catch (Exception e) {
|
|
|
2039 |
ExceptionHandler.handle(MainFrame.getInstance(), "Unable to stop " + f, e);
|
|
|
2040 |
}
|
|
|
2041 |
}
|
|
|
2042 |
});
|
|
|
2043 |
}
|
|
|
2044 |
} catch (Exception e) {
|
|
|
2045 |
throw new IllegalStateException("Couldn't stop module " + m, e);
|
|
|
2046 |
}
|
73 |
ilm |
2047 |
// we can't undo what the module has done, so just start from the base menu and re-apply all
|
|
|
2048 |
// modifications
|
|
|
2049 |
final MenuAndActions menuAndActions = MenuManager.getInstance().createBaseMenuAndActions();
|
|
|
2050 |
final ArrayList<AbstractModule> modules = new ArrayList<AbstractModule>(this.runningModules.values());
|
|
|
2051 |
SwingThreadUtils.invoke(new Runnable() {
|
156 |
ilm |
2052 |
|
73 |
ilm |
2053 |
@Override
|
|
|
2054 |
public void run() {
|
|
|
2055 |
for (final AbstractModule m : modules) {
|
|
|
2056 |
setupMenu(m, menuAndActions);
|
|
|
2057 |
}
|
|
|
2058 |
MenuManager.getInstance().setMenuAndActions(menuAndActions);
|
|
|
2059 |
}
|
156 |
ilm |
2060 |
|
73 |
ilm |
2061 |
});
|
|
|
2062 |
|
18 |
ilm |
2063 |
if (persistent)
|
|
|
2064 |
getRunningIDsPrefs().remove(m.getFactory().getID());
|
|
|
2065 |
assert !this.isModuleRunning(id);
|
156 |
ilm |
2066 |
return true;
|
18 |
ilm |
2067 |
}
|
|
|
2068 |
|
61 |
ilm |
2069 |
private final void stopModule(final AbstractModule m) {
|
80 |
ilm |
2070 |
// this must not attempt to lock this monitor, see uninstallUnsafe()
|
61 |
ilm |
2071 |
assert SwingUtilities.isEventDispatchThread();
|
|
|
2072 |
m.stop();
|
|
|
2073 |
this.tearDownComponents(m);
|
|
|
2074 |
}
|
|
|
2075 |
|
19 |
ilm |
2076 |
private void unregisterSQLElements(final AbstractModule module) {
|
80 |
ilm |
2077 |
final ModuleReference id = module.getFactory().getReference();
|
25 |
ilm |
2078 |
synchronized (this.modulesElements) {
|
|
|
2079 |
if (this.modulesElements.containsKey(id)) {
|
80 |
ilm |
2080 |
final IdentityHashMap<SQLElement, SQLElement> elements = this.modulesElements.remove(id);
|
25 |
ilm |
2081 |
final SQLElementDirectory dir = getDirectory();
|
80 |
ilm |
2082 |
for (final Entry<SQLElement, SQLElement> e : elements.entrySet()) {
|
|
|
2083 |
dir.removeSQLElement(e.getKey());
|
|
|
2084 |
// restore replaced element if any
|
|
|
2085 |
if (e.getValue() != null) {
|
|
|
2086 |
dir.addSQLElement(e.getValue());
|
|
|
2087 |
}
|
|
|
2088 |
}
|
|
|
2089 |
|
|
|
2090 |
final String mdVariant = getMDVariant(module.getFactory());
|
|
|
2091 |
// perhaps record which element this module modified in start()
|
|
|
2092 |
for (final SQLElement elem : this.getDirectory().getElements()) {
|
|
|
2093 |
elem.removeFromMDPath(mdVariant);
|
|
|
2094 |
}
|
|
|
2095 |
getConf().getTranslator().removeDescFor(null, null, mdVariant, null);
|
25 |
ilm |
2096 |
}
|
19 |
ilm |
2097 |
}
|
|
|
2098 |
}
|
|
|
2099 |
|
|
|
2100 |
private void tearDownComponents(final AbstractModule module) {
|
61 |
ilm |
2101 |
assert SwingUtilities.isEventDispatchThread();
|
19 |
ilm |
2102 |
final String id = module.getFactory().getID();
|
|
|
2103 |
if (this.modulesComponents.containsKey(id)) {
|
|
|
2104 |
final ComponentsContext ctxt = this.modulesComponents.remove(id);
|
83 |
ilm |
2105 |
for (final Entry<SQLElement, ? extends Collection<String>> e : ctxt.getFields().entrySet())
|
19 |
ilm |
2106 |
for (final String fieldName : e.getValue())
|
|
|
2107 |
e.getKey().removeAdditionalField(fieldName);
|
83 |
ilm |
2108 |
for (final Entry<SQLElement, ? extends Collection<IListeAction>> e : ctxt.getRowActions().entrySet())
|
19 |
ilm |
2109 |
e.getKey().getRowActions().removeAll(e.getValue());
|
177 |
ilm |
2110 |
TranslationManager.removeTranslationStreamFromClass(module.getClass());
|
73 |
ilm |
2111 |
// can't undo so menu is reset in stopModule()
|
19 |
ilm |
2112 |
}
|
|
|
2113 |
}
|
|
|
2114 |
|
80 |
ilm |
2115 |
private final List<ModuleReference> getDBDependentModules(final ModuleReference ref) throws Exception {
|
|
|
2116 |
// dependencies are stored in the DB that way we can uninstall dependent modules even
|
|
|
2117 |
// without their factories
|
73 |
ilm |
2118 |
|
80 |
ilm |
2119 |
final SQLTable installedTable = getInstalledTable(getRoot());
|
|
|
2120 |
final TableRef needingModule = new AliasedTable(installedTable, "needingModule");
|
|
|
2121 |
final SQLTable depT = getDepTable();
|
|
|
2122 |
|
|
|
2123 |
final SQLSelect sel = new SQLSelect();
|
|
|
2124 |
sel.setWhere(getModuleRowWhere(installedTable).and(new Where(installedTable.getField(MODULE_COLNAME), "=", ref.getID())));
|
|
|
2125 |
if (ref.getVersion() != null)
|
|
|
2126 |
sel.andWhere(new Where(installedTable.getField(MODULE_VERSION_COLNAME), "=", ref.getVersion().getMerged()));
|
|
|
2127 |
sel.addBackwardJoin("INNER", depT.getField(NEEDED_MODULE_COLNAME), null);
|
|
|
2128 |
sel.addJoin("INNER", new Where(depT.getField(NEEDING_MODULE_COLNAME), "=", needingModule.getKey()));
|
|
|
2129 |
sel.addSelect(needingModule.getKey());
|
|
|
2130 |
sel.addSelect(needingModule.getField(MODULE_COLNAME));
|
|
|
2131 |
sel.addSelect(needingModule.getField(MODULE_VERSION_COLNAME));
|
|
|
2132 |
|
|
|
2133 |
@SuppressWarnings("unchecked")
|
|
|
2134 |
final List<Map<String, Object>> rows = installedTable.getDBSystemRoot().getDataSource().execute(sel.asString());
|
|
|
2135 |
final List<ModuleReference> res = new ArrayList<ModuleReference>(rows.size());
|
|
|
2136 |
for (final Map<String, Object> row : rows) {
|
|
|
2137 |
res.add(getRef(new SQLRow(needingModule.getTable(), row)));
|
19 |
ilm |
2138 |
}
|
80 |
ilm |
2139 |
return res;
|
19 |
ilm |
2140 |
}
|
|
|
2141 |
|
80 |
ilm |
2142 |
// ATTN the result is not in removal order since it might contain itself dependent modules, e.g.
|
|
|
2143 |
// getDependentModules(C) will return A, B but the removal order is B, A :
|
|
|
2144 |
// A
|
|
|
2145 |
// ^
|
|
|
2146 |
// |> C
|
|
|
2147 |
// B
|
|
|
2148 |
private synchronized final Set<ModuleReference> getDependentModules(final ModuleReference ref) throws Exception {
|
|
|
2149 |
// predictable order
|
|
|
2150 |
final Set<ModuleReference> res = new TreeSet<ModuleReference>(ModuleReference.COMP_ID_ASC_VERSION_DESC);
|
|
|
2151 |
// ATTN if in the future we make local-only modules, we will have to record the dependencies
|
|
|
2152 |
// in the local file system
|
|
|
2153 |
res.addAll(getDBDependentModules(ref));
|
|
|
2154 |
return res;
|
|
|
2155 |
}
|
|
|
2156 |
|
19 |
ilm |
2157 |
/**
|
80 |
ilm |
2158 |
* The list of installed modules depending on the passed one.
|
19 |
ilm |
2159 |
*
|
80 |
ilm |
2160 |
* @param ref the module.
|
|
|
2161 |
* @return the modules needing <code>ref</code> (excluding it), in uninstallation order (i.e.
|
|
|
2162 |
* the first item isn't depended on).
|
19 |
ilm |
2163 |
* @throws Exception if an error occurs.
|
|
|
2164 |
*/
|
80 |
ilm |
2165 |
public final List<ModuleReference> getDependentModulesRecursively(final ModuleReference ref) throws Exception {
|
|
|
2166 |
return getDependentModulesRecursively(ref, new ArrayList<ModuleReference>());
|
|
|
2167 |
}
|
67 |
ilm |
2168 |
|
80 |
ilm |
2169 |
private synchronized final List<ModuleReference> getDependentModulesRecursively(final ModuleReference ref, final List<ModuleReference> res) throws Exception {
|
|
|
2170 |
for (final ModuleReference depModule : getDependentModules(ref)) {
|
|
|
2171 |
// can happen if a module depends on two others and they share a dependency, e.g.
|
|
|
2172 |
// __ B
|
|
|
2173 |
// A < > D
|
|
|
2174 |
// __ C
|
|
|
2175 |
if (!res.contains(depModule)) {
|
|
|
2176 |
// the graph has no cycle, so we don't need to protected against infinite loop
|
|
|
2177 |
final List<ModuleReference> depModules = this.getDependentModulesRecursively(depModule, res);
|
|
|
2178 |
assert !depModules.contains(depModule) : "cycle with " + depModule;
|
|
|
2179 |
res.add(depModule);
|
|
|
2180 |
}
|
19 |
ilm |
2181 |
}
|
|
|
2182 |
return res;
|
|
|
2183 |
}
|
|
|
2184 |
|
|
|
2185 |
// ids + modules depending on them in uninstallation order
|
80 |
ilm |
2186 |
// ATTN return ids even if not installed
|
|
|
2187 |
synchronized final LinkedHashSet<ModuleReference> getAllOrderedDependentModulesRecursively(final Set<ModuleReference> ids) throws Exception {
|
|
|
2188 |
final LinkedHashSet<ModuleReference> depModules = new LinkedHashSet<ModuleReference>();
|
|
|
2189 |
for (final ModuleReference id : ids) {
|
19 |
ilm |
2190 |
if (!depModules.contains(id)) {
|
|
|
2191 |
depModules.addAll(getDependentModulesRecursively(id));
|
|
|
2192 |
// even without this line the result could still contain some of ids if it contained
|
|
|
2193 |
// a module and one of its dependencies
|
|
|
2194 |
depModules.add(id);
|
|
|
2195 |
}
|
|
|
2196 |
}
|
|
|
2197 |
return depModules;
|
|
|
2198 |
}
|
|
|
2199 |
|
80 |
ilm |
2200 |
public synchronized final Set<ModuleReference> uninstall(final Set<ModuleReference> ids, final boolean recurse) throws Exception {
|
|
|
2201 |
return this.uninstall(ids, recurse, false);
|
19 |
ilm |
2202 |
}
|
|
|
2203 |
|
80 |
ilm |
2204 |
public synchronized final Set<ModuleReference> uninstall(final Set<ModuleReference> ids, final boolean recurse, final boolean force) throws Exception {
|
|
|
2205 |
return this.applyChange(this.getUninstallSolution(ids, recurse, force), ModuleState.NOT_CREATED).getRemoved();
|
19 |
ilm |
2206 |
}
|
|
|
2207 |
|
80 |
ilm |
2208 |
// ATTN this doesn't use canCurrentUserInstall(), as (at least for now) there's one and only one
|
|
|
2209 |
// solution. That way, the UI can list the modules that need to be uninstalled.
|
|
|
2210 |
public synchronized final ModulesStateChange getUninstallSolution(final Set<ModuleReference> passedRefs, final boolean recurse, final boolean force) throws Exception {
|
|
|
2211 |
// compute now, at the same time as the solution not in each
|
|
|
2212 |
// ModulesStateChange.getInstallState()
|
|
|
2213 |
final InstallationState installationState = new InstallationState(this);
|
19 |
ilm |
2214 |
|
80 |
ilm |
2215 |
final Set<ModuleReference> ids = new HashSet<ModuleReference>();
|
|
|
2216 |
for (final ModuleReference ref : passedRefs) {
|
|
|
2217 |
if (ref.getVersion() == null)
|
|
|
2218 |
throw new UnsupportedOperationException("Version needed for " + ref);
|
|
|
2219 |
if (installationState.getLocalOrRemote().contains(ref)) {
|
|
|
2220 |
ids.add(ref);
|
19 |
ilm |
2221 |
}
|
|
|
2222 |
}
|
|
|
2223 |
|
80 |
ilm |
2224 |
final int size = ids.size();
|
|
|
2225 |
final Set<ModuleReference> toRemove;
|
|
|
2226 |
// optimize by not calling recursively getDependentModules()
|
|
|
2227 |
if (!recurse && size == 1) {
|
|
|
2228 |
final Set<ModuleReference> depModules = this.getDependentModules(ids.iterator().next());
|
|
|
2229 |
if (depModules.size() > 0)
|
|
|
2230 |
throw new IllegalStateException("Dependent modules not uninstalled : " + depModules);
|
|
|
2231 |
toRemove = ids;
|
|
|
2232 |
} else if (size > 0) {
|
|
|
2233 |
toRemove = getAllOrderedDependentModulesRecursively(ids);
|
|
|
2234 |
} else {
|
|
|
2235 |
toRemove = Collections.emptySet();
|
|
|
2236 |
}
|
|
|
2237 |
// if size == 1, already tested
|
|
|
2238 |
if (!recurse && size > 1) {
|
|
|
2239 |
final Collection<ModuleReference> depModulesNotRequested = CollectionUtils.substract(toRemove, ids);
|
|
|
2240 |
if (!depModulesNotRequested.isEmpty())
|
|
|
2241 |
throw new IllegalStateException("Dependent modules not uninstalled : " + depModulesNotRequested);
|
|
|
2242 |
}
|
|
|
2243 |
return new ModulesStateChange() {
|
19 |
ilm |
2244 |
|
80 |
ilm |
2245 |
@Override
|
|
|
2246 |
public String getError() {
|
|
|
2247 |
return null;
|
|
|
2248 |
}
|
19 |
ilm |
2249 |
|
80 |
ilm |
2250 |
@Override
|
|
|
2251 |
public InstallationState getInstallState() {
|
|
|
2252 |
return installationState;
|
|
|
2253 |
}
|
18 |
ilm |
2254 |
|
80 |
ilm |
2255 |
@Override
|
|
|
2256 |
public Set<ModuleReference> getUserReferencesToInstall() {
|
|
|
2257 |
return Collections.emptySet();
|
|
|
2258 |
}
|
19 |
ilm |
2259 |
|
80 |
ilm |
2260 |
@Override
|
|
|
2261 |
public Set<ModuleReference> getReferencesToRemove() {
|
|
|
2262 |
return toRemove;
|
|
|
2263 |
}
|
67 |
ilm |
2264 |
|
80 |
ilm |
2265 |
@Override
|
|
|
2266 |
public boolean forceRemove() {
|
|
|
2267 |
return force;
|
67 |
ilm |
2268 |
}
|
80 |
ilm |
2269 |
|
|
|
2270 |
@Override
|
|
|
2271 |
public Set<ModuleReference> getReferencesToInstall() {
|
|
|
2272 |
return Collections.emptySet();
|
67 |
ilm |
2273 |
}
|
|
|
2274 |
|
80 |
ilm |
2275 |
@Override
|
|
|
2276 |
public DepSolverGraph getGraph() {
|
|
|
2277 |
return null;
|
|
|
2278 |
}
|
156 |
ilm |
2279 |
|
|
|
2280 |
@Override
|
|
|
2281 |
public String toString() {
|
|
|
2282 |
return "Uninstall solution for " + this.getReferencesToRemove();
|
|
|
2283 |
}
|
80 |
ilm |
2284 |
};
|
67 |
ilm |
2285 |
}
|
19 |
ilm |
2286 |
|
80 |
ilm |
2287 |
public final void uninstall(final ModuleReference ref) throws Exception {
|
|
|
2288 |
this.uninstall(ref, false);
|
67 |
ilm |
2289 |
}
|
|
|
2290 |
|
80 |
ilm |
2291 |
public synchronized final Set<ModuleReference> uninstall(final ModuleReference id, final boolean recurse) throws Exception {
|
|
|
2292 |
return this.uninstall(id, recurse, false);
|
67 |
ilm |
2293 |
}
|
|
|
2294 |
|
80 |
ilm |
2295 |
public synchronized final Set<ModuleReference> uninstall(final ModuleReference id, final boolean recurse, final boolean force) throws Exception {
|
|
|
2296 |
return this.uninstall(Collections.singleton(id), recurse, force);
|
18 |
ilm |
2297 |
}
|
67 |
ilm |
2298 |
|
156 |
ilm |
2299 |
// return the version in installed that matches ref
|
|
|
2300 |
private final ModuleReference filter(final Set<ModuleReference> installed, final ModuleReference ref) {
|
|
|
2301 |
for (final ModuleReference installedRef : installed) {
|
|
|
2302 |
if (installedRef.getID().equals(ref.getID()) && (ref.getVersion() == null || installedRef.getVersion().equals(ref.getVersion())))
|
|
|
2303 |
return installedRef;
|
|
|
2304 |
}
|
|
|
2305 |
return null;
|
67 |
ilm |
2306 |
}
|
|
|
2307 |
|
80 |
ilm |
2308 |
// unsafe because this method doesn't check dependents
|
|
|
2309 |
// dbVersions parameter to avoid requests to the DB
|
|
|
2310 |
// return true if the mref was actually uninstalled (i.e. it was installed locally or remotely)
|
156 |
ilm |
2311 |
private boolean uninstallUnsafe(final ModuleReference mref, final boolean requireModule, final InstallationState installState) throws SQLException, Exception {
|
80 |
ilm |
2312 |
assert Thread.holdsLock(this);
|
|
|
2313 |
final String id = mref.getID();
|
|
|
2314 |
// versions to uninstall
|
156 |
ilm |
2315 |
final ModuleReference localRef = filter(installState.getLocal(), mref);
|
|
|
2316 |
final ModuleReference dbRef = filter(installState.getRemote(), mref);
|
|
|
2317 |
final ModuleVersion dbVersion = dbRef == null ? null : dbRef.getVersion();
|
67 |
ilm |
2318 |
|
80 |
ilm |
2319 |
// otherwise it will get re-installed the next launch
|
|
|
2320 |
getRunningIDsPrefs().remove(id);
|
|
|
2321 |
final Set<ModuleReference> refs = new HashSet<ModuleReference>(2);
|
156 |
ilm |
2322 |
if (localRef != null)
|
|
|
2323 |
refs.add(localRef);
|
|
|
2324 |
if (dbRef != null)
|
|
|
2325 |
refs.add(dbRef);
|
80 |
ilm |
2326 |
setAdminRequiredModules(refs, false);
|
67 |
ilm |
2327 |
|
80 |
ilm |
2328 |
// only return after having cleared required, so that we don't need to install just to
|
|
|
2329 |
// not require
|
156 |
ilm |
2330 |
if (localRef == null && dbRef == null)
|
80 |
ilm |
2331 |
return false;
|
67 |
ilm |
2332 |
|
156 |
ilm |
2333 |
if (dbRef != null && !currentUserIsAdmin())
|
80 |
ilm |
2334 |
throw new IllegalStateException("Not allowed to uninstall " + id + " from the database");
|
67 |
ilm |
2335 |
|
80 |
ilm |
2336 |
// DB module
|
|
|
2337 |
final AbstractModule module;
|
|
|
2338 |
if (!this.isModuleRunning(id)) {
|
156 |
ilm |
2339 |
if (dbRef == null) {
|
|
|
2340 |
assert localRef != null;
|
80 |
ilm |
2341 |
// only installed locally
|
|
|
2342 |
module = null;
|
|
|
2343 |
} else {
|
|
|
2344 |
final SortedMap<ModuleVersion, ModuleFactory> available = this.factories.getVersions(id);
|
|
|
2345 |
final ModuleReference ref;
|
|
|
2346 |
if (available.containsKey(dbVersion)) {
|
156 |
ilm |
2347 |
ref = dbRef;
|
80 |
ilm |
2348 |
} else {
|
|
|
2349 |
// perhaps modules should specify which versions they can uninstall
|
|
|
2350 |
final SortedMap<ModuleVersion, ModuleFactory> moreRecent = available.headMap(dbVersion);
|
|
|
2351 |
if (moreRecent.size() == 0) {
|
|
|
2352 |
ref = null;
|
|
|
2353 |
} else {
|
|
|
2354 |
// take the closest
|
|
|
2355 |
ref = new ModuleReference(id, moreRecent.lastKey());
|
|
|
2356 |
}
|
|
|
2357 |
}
|
|
|
2358 |
if (ref != null) {
|
|
|
2359 |
assert ref.getVersion().compareTo(dbVersion) >= 0;
|
|
|
2360 |
final ModuleFactory f = available.get(ref.getVersion());
|
|
|
2361 |
assert f != null;
|
|
|
2362 |
// only call expensive method if necessary
|
|
|
2363 |
if (!this.createdModules.containsKey(f)) {
|
156 |
ilm |
2364 |
// * Don't use the result, instead use this.createdModules since the module
|
|
|
2365 |
// might have been created before.
|
|
|
2366 |
// * Cannot call directly applyChange(), we need DepSolver to create modules
|
|
|
2367 |
// that ref depends on, as they might be required by
|
|
|
2368 |
// AbstractModule.uninstall().
|
|
|
2369 |
// * Cannot pass NoChoicePredicate.NO_CHANGE as ref won't be created if not
|
|
|
2370 |
// already installed both locally and remotely. No installation will occur
|
|
|
2371 |
// since we pass ModuleState.CREATED.
|
|
|
2372 |
this.createModules(Collections.singleton(ref), NoChoicePredicate.ONLY_INSTALL, ModuleState.CREATED);
|
80 |
ilm |
2373 |
}
|
|
|
2374 |
module = this.createdModules.get(f);
|
|
|
2375 |
} else {
|
|
|
2376 |
module = null;
|
|
|
2377 |
}
|
|
|
2378 |
if (module == null && requireModule) {
|
|
|
2379 |
final String reason;
|
|
|
2380 |
if (ref == null) {
|
|
|
2381 |
reason = "No version recent enough to uninstall " + dbVersion + " : " + available.keySet();
|
|
|
2382 |
} else {
|
|
|
2383 |
// TODO include InvalidRef in ModulesStateChangeResult
|
|
|
2384 |
reason = "Creation of " + ref + " failed (e.g. missing factory, dependency)";
|
|
|
2385 |
}
|
|
|
2386 |
throw new IllegalStateException("Couldn't get module " + id + " : " + reason);
|
|
|
2387 |
}
|
73 |
ilm |
2388 |
}
|
80 |
ilm |
2389 |
} else {
|
156 |
ilm |
2390 |
final ModuleVersion localVersion = localRef.getVersion();
|
80 |
ilm |
2391 |
if (!localVersion.equals(dbVersion))
|
|
|
2392 |
L.warning("Someone else has changed the database version while we were running :" + localVersion + " != " + dbVersion);
|
|
|
2393 |
module = this.runningModules.get(id);
|
|
|
2394 |
assert localVersion.equals(module.getFactory().getVersion());
|
|
|
2395 |
this.stopModule(id, false);
|
|
|
2396 |
// The module has to be stopped before we can proceed
|
|
|
2397 |
// ATTN we hold this monitor, so stop() should never try to acquire it in the EDT
|
|
|
2398 |
if (!SwingUtilities.isEventDispatchThread()) {
|
|
|
2399 |
SwingUtilities.invokeAndWait(EMPTY_RUNNABLE);
|
|
|
2400 |
}
|
67 |
ilm |
2401 |
}
|
156 |
ilm |
2402 |
assert (module == null) == (!requireModule || dbRef == null);
|
67 |
ilm |
2403 |
|
80 |
ilm |
2404 |
SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
|
|
|
2405 |
@Override
|
|
|
2406 |
public Object create() throws SQLException {
|
|
|
2407 |
final DBRoot root = getRoot();
|
|
|
2408 |
if (module != null) {
|
|
|
2409 |
module.uninstall(root);
|
|
|
2410 |
unregisterSQLElements(module);
|
|
|
2411 |
}
|
156 |
ilm |
2412 |
if (localRef != null)
|
|
|
2413 |
setModuleInstalledLocally(localRef, false);
|
67 |
ilm |
2414 |
|
80 |
ilm |
2415 |
// uninstall from DB
|
156 |
ilm |
2416 |
if (dbRef != null) {
|
80 |
ilm |
2417 |
final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
|
|
|
2418 |
final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
|
|
|
2419 |
final Set<String> tableNames = createdItems.get0();
|
|
|
2420 |
for (final SQLName field : createdItems.get1()) {
|
|
|
2421 |
final SQLField f = root.getDesc(field, SQLField.class);
|
|
|
2422 |
// dropped by DROP TABLE
|
|
|
2423 |
if (!tableNames.contains(f.getTable().getName())) {
|
|
|
2424 |
// cascade needed since the module might have created constraints
|
|
|
2425 |
// (e.g. on H2 a foreign column cannot be dropped)
|
|
|
2426 |
l.add(new AlterTable(f.getTable()).dropColumnCascade(f.getName()));
|
|
|
2427 |
}
|
|
|
2428 |
}
|
|
|
2429 |
for (final String table : tableNames) {
|
|
|
2430 |
l.add(new DropTable(root.getTable(table)));
|
|
|
2431 |
}
|
|
|
2432 |
if (l.size() > 0) {
|
|
|
2433 |
for (final String s : ChangeTable.cat(l, root.getName()))
|
|
|
2434 |
root.getDBSystemRoot().getDataSource().execute(s);
|
|
|
2435 |
root.getSchema().updateVersion();
|
|
|
2436 |
root.refetch();
|
|
|
2437 |
}
|
67 |
ilm |
2438 |
|
156 |
ilm |
2439 |
removeModuleFields(dbRef);
|
80 |
ilm |
2440 |
}
|
|
|
2441 |
return null;
|
|
|
2442 |
}
|
|
|
2443 |
});
|
|
|
2444 |
return true;
|
67 |
ilm |
2445 |
}
|
18 |
ilm |
2446 |
}
|