]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java
Improve runtime manager
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / osgi / OsgiRuntimeContext.java
1 package org.argeo.init.osgi;
2
3 import java.io.Serializable;
4 import java.lang.System.Logger;
5 import java.lang.System.Logger.Level;
6 import java.lang.System.LoggerFinder;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.Hashtable;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Objects;
13 import java.util.Optional;
14 import java.util.ServiceLoader;
15 import java.util.concurrent.Flow;
16 import java.util.function.Consumer;
17 import java.util.function.Supplier;
18
19 import org.argeo.api.init.InitConstants;
20 import org.argeo.api.init.RuntimeContext;
21 import org.osgi.framework.BundleContext;
22 import org.osgi.framework.BundleException;
23 import org.osgi.framework.Constants;
24 import org.osgi.framework.ServiceReference;
25 import org.osgi.framework.connect.ConnectFrameworkFactory;
26 import org.osgi.framework.launch.Framework;
27
28 /** An OSGi runtime context. */
29 public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
30 private final static Logger logger = System.getLogger(OsgiRuntimeContext.class.getName());
31
32 private final static long STOP_FOR_UPDATE_TIMEOUT = 60 * 1000;
33 private final static long CLOSE_TIMEOUT = 60 * 1000;
34
35 private final ConnectFrameworkFactory frameworkFactory;
36 private Map<String, String> config;
37 private Framework framework;
38
39 // Nested runtimes
40 // private final BundleContext foreignBundleContext;
41 // private final List<String> foreignCategories;
42
43 private final ForeignModuleConnector moduleConnector;
44
45 /**
46 * Constructor to use when the runtime context will create the OSGi
47 * {@link Framework}.
48 */
49 public OsgiRuntimeContext(ConnectFrameworkFactory frameworkFactory, Map<String, String> config) {
50 this(frameworkFactory, config, null, null);
51 }
52
53 /**
54 * Constructor to use when the runtime context will create the OSGi
55 * {@link Framework} and will potentially have a parent runtime.
56 */
57 OsgiRuntimeContext(ConnectFrameworkFactory frameworkFactory, Map<String, String> config,
58 BundleContext foreignBundleContext, List<String> foreignCategories) {
59 this.frameworkFactory = frameworkFactory;
60 this.config = config;
61 if (foreignCategories != null) {
62 // String parentCategories = config.get(InitConstants.PROP_ARGEO_OSGI_PARENT_CATEGORIES);
63 // if (parentCategories != null) {
64 // Objects.requireNonNull(foreignBundleContext, "Foreign bundle context");
65 // List<String> foreignCategories = Arrays.asList(parentCategories.trim().split(","));
66 // foreignCategories.removeIf((s) -> "".equals(s));
67 this.moduleConnector = new ForeignModuleConnector(foreignBundleContext, foreignCategories);
68 } else {
69 this.moduleConnector = null;
70 }
71 }
72
73 /**
74 * Constructor to use when the OSGi {@link Framework} has been created by other
75 * means.
76 */
77 OsgiRuntimeContext(BundleContext bundleContext) {
78 this.frameworkFactory = null;
79 // TODO try nesting within Eclipse PDE
80 this.moduleConnector = null;
81 start(bundleContext);
82 }
83
84 @Override
85 public void run() {
86 if (framework != null && framework.getState() >= Framework.STARTING)
87 throw new IllegalStateException("OSGi framework is already started");
88
89 if (framework == null) {
90 // ServiceLoader<FrameworkFactory> sl = ServiceLoader.load(FrameworkFactory.class);
91 // Optional<FrameworkFactory> opt = sl.findFirst();
92 // if (opt.isEmpty())
93 // throw new IllegalStateException("Cannot find OSGi framework");
94 // framework = opt.get().newFramework(config);
95 framework = frameworkFactory.newFramework(config, moduleConnector);
96 }
97
98 try {
99 framework.start();
100 BundleContext bundleContext = framework.getBundleContext();
101 bundleContext.registerService(ConnectFrameworkFactory.class, frameworkFactory, null);
102 start(bundleContext);
103 } catch (BundleException e) {
104 throw new IllegalStateException("Cannot start OSGi framework", e);
105 }
106 }
107
108 protected void start(BundleContext bundleContext) {
109 // preferences
110 // SystemRootPreferences systemRootPreferences = ThinPreferencesFactory.getInstance().getSystemRootPreferences();
111 // bundleContext.registerService(AbstractPreferences.class, systemRootPreferences, new Hashtable<>());
112
113 // Make sure LoggerFinder has been searched for, since it is lazily loaded
114 LoggerFinder loggerFinder = LoggerFinder.getLoggerFinder();
115
116 if (loggerFinder instanceof Consumer<?> && loggerFinder instanceof Supplier<?>) {
117 @SuppressWarnings("unchecked")
118 Consumer<Map<String, Object>> consumer = (Consumer<Map<String, Object>>) loggerFinder;
119 // ThinLoggerFinder.getConfigurationConsumer()
120 // ThinLoggerFinder.getLogEntryPublisher()
121
122 @SuppressWarnings("unchecked")
123 Supplier<Flow.Publisher<Map<String, Serializable>>> supplier = (Supplier<Flow.Publisher<Map<String, Serializable>>>) loggerFinder;
124 // logging
125 bundleContext.registerService(Consumer.class, consumer,
126 new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
127 bundleContext.registerService(Flow.Publisher.class, supplier.get(),
128 new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
129 }
130 OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
131 String frameworkUuuid = bundleContext.getProperty(Constants.FRAMEWORK_UUID);
132
133 // osgiBoot.bootstrap();
134
135 // separate thread in order to improve logging
136 Thread osgiBootThread = new Thread("OSGi boot framework " + frameworkUuuid) {
137 @Override
138 public void run() {
139 osgiBoot.bootstrap();
140 }
141 };
142 osgiBootThread.start();
143 // TODO return a completable stage so that inits can run in parallel
144 // try {
145 // osgiBootThread.join(60 * 1000);
146 // } catch (InterruptedException e) {
147 // // silent
148 // }
149 }
150
151 public void update() {
152 stop();
153 try {
154 waitForStop(STOP_FOR_UPDATE_TIMEOUT);
155 } catch (InterruptedException e) {
156 logger.log(Level.TRACE, "Wait for stop interrupted", e);
157 }
158 run();
159
160 // TODO Optimise with OSGi mechanisms (e.g. framework.update())
161 // if (osgiBoot != null) {
162 // Objects.requireNonNull(osgiBoot);
163 // osgiBoot.update();
164 // }
165 }
166
167 protected void stop() {
168 if (framework == null)
169 return;
170 stop(framework.getBundleContext());
171 try {
172 framework.stop();
173 } catch (BundleException e) {
174 throw new IllegalStateException("Cannot stop OSGi framework", e);
175 }
176 }
177
178 protected void stop(BundleContext bundleContext) {
179 // if (loggingConfigurationSr != null)
180 // try {
181 // loggingConfigurationSr.unregister();
182 // } catch (Exception e) {
183 // // silent
184 // }
185 // if (logEntryPublisherSr != null)
186 // try {
187 // logEntryPublisherSr.unregister();
188 // } catch (Exception e) {
189 // // silent
190 // }
191 }
192
193 @Override
194 public void waitForStop(long timeout) throws InterruptedException {
195 if (framework == null)
196 return;
197
198 framework.waitForStop(timeout);
199 }
200
201 public void close() throws Exception {
202 if (framework == null)
203 return;
204 // TODO make shutdown of dynamic service more robust
205 // for (Bundle scrBundle : framework.getBundleContext().getBundles()) {
206 // if (scrBundle.getSymbolicName().equals(SYMBOLIC_NAME_FELIX_SCR)) {
207 // if (scrBundle.getState() > Bundle.RESOLVED) {
208 // scrBundle.stop();
209 // while (!(scrBundle.getState() <= Bundle.RESOLVED)) {
210 // Thread.sleep(100);
211 // }
212 // Thread.sleep(500);
213 // }
214 // }
215 // }
216
217 stop();
218 waitForStop(CLOSE_TIMEOUT);
219 framework = null;
220 // osgiBoot = null;
221 config.clear();
222 }
223
224 public Framework getFramework() {
225 return framework;
226 }
227
228 /**
229 * Load {@link ConnectFrameworkFactory} from Java service loader. This will not
230 * work within a pure OSGi runtime, so the reference should be passed to child
231 * runtimes as an OSGi service.
232 */
233 public static ConnectFrameworkFactory loadFrameworkFactory() {
234 ServiceLoader<ConnectFrameworkFactory> sl = ServiceLoader.load(ConnectFrameworkFactory.class);
235 Optional<ConnectFrameworkFactory> opt = sl.findFirst();
236 if (opt.isEmpty())
237 throw new IllegalStateException("Cannot find OSGi framework factory");
238 return opt.get();
239 }
240
241 public static ConnectFrameworkFactory getFrameworkFactory(BundleContext bundleContext) {
242 ServiceReference<ConnectFrameworkFactory> sr = bundleContext.getServiceReference(ConnectFrameworkFactory.class);
243 if (sr == null)
244 return null;
245 return bundleContext.getService(sr);
246 }
247 }