]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java
Improve runtime manager
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / osgi / OsgiRuntimeManager.java
1 package org.argeo.init.osgi;
2
3 import java.lang.System.Logger;
4 import java.lang.System.Logger.Level;
5 import java.net.URI;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.Paths;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Objects;
16 import java.util.TreeMap;
17 import java.util.function.Consumer;
18
19 import org.argeo.api.init.InitConstants;
20 import org.argeo.api.init.RuntimeContext;
21 import org.argeo.api.init.RuntimeManager;
22 import org.argeo.internal.init.InternalState;
23 import org.osgi.framework.BundleContext;
24 import org.osgi.framework.FrameworkEvent;
25 import org.osgi.framework.connect.ConnectFrameworkFactory;
26 import org.osgi.framework.launch.Framework;
27
28 /**
29 * Dynamically configures and launches multiple runtimes.
30 */
31 class OsgiRuntimeManager implements RuntimeManager {
32 private final static Logger logger = System.getLogger(OsgiRuntimeManager.class.getName());
33
34 private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000;
35
36 // private Path ownConfigArea;
37
38 private Path baseConfigArea;
39 private Path baseWritableArea;
40 // private Map<String, String> configuration = new HashMap<>();
41
42 private ConnectFrameworkFactory frameworkFactory;
43
44 private final BundleContext bundleContext;
45
46 private Map<String, OsgiRuntimeContext> runtimeContexts = new TreeMap<>();
47
48 private boolean useForeignRuntime = Boolean
49 .parseBoolean(System.getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_ENABLED, "true"));
50
51 OsgiRuntimeManager(BundleContext bundleContext) {
52 Objects.requireNonNull(bundleContext);
53 this.bundleContext = bundleContext;
54 frameworkFactory = OsgiRuntimeContext.getFrameworkFactory(bundleContext);
55 this.baseConfigArea = Paths
56 .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA)));
57 // this.baseConfigArea = ownConfigArea.getParent();
58 this.baseWritableArea = Paths
59 .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA))).getParent();
60
61 // logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration);
62
63 // System.out.println("java.library.path=" + System.getProperty("java.library.path"));
64 }
65
66 protected void shutdown() {
67 // shutdown runtimes
68 Map<String, RuntimeContext> shutdowning = new HashMap<>(runtimeContexts);
69 for (String id : new HashSet<>(runtimeContexts.keySet())) {
70 logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ...");
71 closeRuntime(id, true);
72 }
73 for (String id : shutdowning.keySet())
74 try {
75 RuntimeContext runtimeContext = shutdowning.get(id);
76 runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT);
77 } catch (InterruptedException e) {
78 // silent
79 } catch (Exception e) {
80 logger.log(Logger.Level.DEBUG, "Cannot wait for " + id + " to shutdown", e);
81 }
82 // shutdown manager runtime
83 try {
84 InternalState.getMainRuntimeContext().close();
85 InternalState.getMainRuntimeContext().waitForStop(RUNTIME_SHUTDOWN_TIMEOUT);
86 // logger.log(Logger.Level.INFO, "Argeo Init stopped with PID " + ProcessHandle.current().pid());
87 System.out.flush();
88 } catch (Exception e) {
89 e.printStackTrace();
90 Runtime.getRuntime().halt(1);
91 }
92 }
93
94 OsgiRuntimeContext loadRuntime(String relPath, Consumer<Map<String, String>> configCallback) {
95 closeRuntime(relPath, false);
96
97 BundleContext foreignBundleContext = null;
98 if (useForeignRuntime) {
99 foreignBundleContext = bundleContext;
100
101 Path parentRelPath = Paths.get(relPath).getParent();
102 if (parentRelPath != null && Files.exists(baseConfigArea.resolve(parentRelPath))) {
103 if (!runtimeContexts.containsKey(parentRelPath.toString())) {
104
105 String exportCategories = bundleContext
106 .getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_CATEGORIES);
107 List<String> foreignCategories = exportCategories == null ? new ArrayList<>()
108 : Arrays.asList(exportCategories.trim().split(","));
109 Path writableArea = baseWritableArea.resolve(parentRelPath);
110 Path configArea = baseConfigArea.resolve(parentRelPath);
111 Map<String, String> config = new HashMap<>();
112 RuntimeManager.loadDefaults(config);
113 config.put(InitConstants.PROP_OSGI_USE_SYSTEM_PROPERTIES, "false");
114 config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA,
115 writableArea.resolve(STATE).toUri().toString());
116 config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea.toUri().toString());
117 config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA_RO, "true");
118 OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(frameworkFactory, config,
119 foreignBundleContext, foreignCategories);
120 runtimeContexts.put(parentRelPath.toString(), runtimeContext);
121 runtimeContext.run();
122 // FIXME properly stage installation
123 try {
124 Thread.sleep(1000);
125 } catch (InterruptedException e) {
126 // silent
127 }
128 }
129 OsgiRuntimeContext parentRuntimeContext = runtimeContexts.get(parentRelPath.toString());
130 foreignBundleContext = parentRuntimeContext.getFramework().getBundleContext();
131 }
132 }
133 Path writableArea = baseWritableArea.resolve(relPath);
134 Path configArea = baseConfigArea.resolve(relPath).getParent().resolve(SHARED);
135 Map<String, String> config = new HashMap<>();
136 // RuntimeManager.loadConfig(configArea, config);
137 RuntimeManager.loadDefaults(config);
138 config.put(InitConstants.PROP_OSGI_USE_SYSTEM_PROPERTIES, "false");
139 config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, writableArea.resolve(STATE).toUri().toString());
140 config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea.toUri().toString());
141 config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA_RO, "true");
142
143 if (configCallback != null)
144 configCallback.accept(config);
145
146 // use config area if instance area is not set
147 if (!config.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA))
148 config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString());
149
150 // create framework
151 OsgiRuntimeContext runtimeContext;
152 if (useForeignRuntime) {
153 String exportCategories = bundleContext.getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_CATEGORIES);
154 List<String> foreignCategories = exportCategories == null ? new ArrayList<>()
155 : Arrays.asList(exportCategories.trim().split(","));
156 runtimeContext = new OsgiRuntimeContext(frameworkFactory, config, foreignBundleContext, foreignCategories);
157 } else {
158 runtimeContext = new OsgiRuntimeContext(frameworkFactory, config);
159 }
160 runtimeContexts.put(relPath, runtimeContext);
161 return runtimeContext;
162 }
163
164 public void startRuntime(String relPath, Consumer<Map<String, String>> configCallback) {
165 OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback);
166 runtimeContext.run();
167 Framework framework = runtimeContext.getFramework();
168 if (framework != null) {// in case the framework has closed very quickly after run
169 framework.getBundleContext().addFrameworkListener((e) -> {
170 if (e.getType() >= FrameworkEvent.STOPPED) {
171 logger.log(Level.DEBUG, "Externally stopped runtime " + relPath + ". Unregistering...", e);
172 runtimeContexts.remove(relPath);
173 }
174 });
175 } else {
176 closeRuntime(relPath, false);
177 }
178 }
179
180 public void closeRuntime(String relPath, boolean async) {
181 if (!runtimeContexts.containsKey(relPath))
182 return;
183 RuntimeContext runtimeContext = runtimeContexts.get(relPath);
184 try {
185 runtimeContext.close();
186 if (!async) {
187 runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT);
188 System.gc();
189 }
190 } catch (Exception e) {
191 logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e);
192 } finally {
193 runtimeContexts.remove(relPath);
194 }
195
196 }
197 }