]> git.argeo.org Git - lgpl/argeo-commons.git/blob - Kernel.java
c82c6c4516049c7354957999f52a96616bd6c78c
[lgpl/argeo-commons.git] / Kernel.java
1 package org.argeo.cms.internal.kernel;
2
3 import static bitronix.tm.TransactionManagerServices.getTransactionManager;
4 import static bitronix.tm.TransactionManagerServices.getTransactionSynchronizationRegistry;
5 import static java.util.Locale.ENGLISH;
6 import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
7 import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstanceDir;
8 import static org.argeo.jcr.ArgeoJcrConstants.ALIAS_NODE;
9 import static org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS;
10 import static org.argeo.util.LocaleChoice.asLocaleList;
11 import static org.osgi.framework.Constants.FRAMEWORK_UUID;
12
13 import java.io.ByteArrayInputStream;
14 import java.io.ByteArrayOutputStream;
15 import java.io.File;
16 import java.io.FileFilter;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.Reader;
20 import java.lang.management.ManagementFactory;
21 import java.net.URL;
22 import java.security.PrivilegedAction;
23 import java.util.Dictionary;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Hashtable;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Set;
31
32 import javax.jcr.ImportUUIDBehavior;
33 import javax.jcr.Repository;
34 import javax.jcr.RepositoryException;
35 import javax.jcr.RepositoryFactory;
36 import javax.jcr.Session;
37 import javax.jcr.SimpleCredentials;
38 import javax.security.auth.Subject;
39 import javax.security.auth.login.LoginContext;
40 import javax.security.auth.login.LoginException;
41 import javax.transaction.TransactionManager;
42 import javax.transaction.TransactionSynchronizationRegistry;
43 import javax.transaction.UserTransaction;
44
45 import org.apache.commons.io.FileUtils;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48 import org.apache.jackrabbit.api.JackrabbitRepository;
49 import org.apache.jackrabbit.commons.cnd.CndImporter;
50 import org.apache.jackrabbit.util.TransientFileFactory;
51 import org.argeo.ArgeoException;
52 import org.argeo.ArgeoLogger;
53 import org.argeo.cms.CmsException;
54 import org.argeo.cms.maintenance.MaintenanceUi;
55 import org.argeo.jackrabbit.JackrabbitDataModel;
56 import org.argeo.jackrabbit.ManagedJackrabbitRepository;
57 import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory;
58 import org.argeo.jcr.ArgeoJcrConstants;
59 import org.argeo.jcr.ArgeoJcrUtils;
60 import org.argeo.jcr.JcrUtils;
61 import org.argeo.jcr.RepoConf;
62 import org.eclipse.equinox.http.jetty.JettyConfigurator;
63 import org.eclipse.equinox.http.jetty.JettyConstants;
64 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
65 import org.eclipse.rap.rwt.application.ApplicationConfiguration;
66 import org.osgi.framework.Bundle;
67 import org.osgi.framework.BundleContext;
68 import org.osgi.framework.Constants;
69 import org.osgi.framework.ServiceEvent;
70 import org.osgi.framework.ServiceListener;
71 import org.osgi.framework.ServiceReference;
72 import org.osgi.framework.ServiceRegistration;
73 import org.osgi.framework.startlevel.BundleStartLevel;
74 import org.osgi.framework.wiring.BundleCapability;
75 import org.osgi.framework.wiring.BundleWire;
76 import org.osgi.framework.wiring.BundleWiring;
77 import org.osgi.service.cm.Configuration;
78 import org.osgi.service.cm.ConfigurationAdmin;
79 import org.osgi.service.cm.ManagedService;
80 import org.osgi.service.log.LogReaderService;
81 import org.osgi.service.useradmin.UserAdmin;
82 import org.osgi.util.tracker.ServiceTracker;
83
84 import bitronix.tm.BitronixTransactionManager;
85 import bitronix.tm.BitronixTransactionSynchronizationRegistry;
86 import bitronix.tm.TransactionManagerServices;
87
88 /**
89 * Argeo CMS Kernel. Responsible for :
90 * <ul>
91 * <li>security</li>
92 * <li>provisioning</li>
93 * <li>transaction</li>
94 * <li>logging</li>
95 * <li>local and remote file systems access</li>
96 * <li>OS access</li>
97 * </ul>
98 */
99 final class Kernel implements KernelHeader, KernelConstants, ServiceListener {
100 /*
101 * SERVICE REFERENCES
102 */
103 private ServiceReference<ConfigurationAdmin> configurationAdmin;
104 /*
105 * REGISTERED SERVICES
106 */
107 private ServiceRegistration<ArgeoLogger> loggerReg;
108 private ServiceRegistration<TransactionManager> tmReg;
109 private ServiceRegistration<UserTransaction> utReg;
110 private ServiceRegistration<TransactionSynchronizationRegistry> tsrReg;
111 private ServiceRegistration<? extends Repository> repositoryReg;
112 private ServiceRegistration<RepositoryFactory> repositoryFactoryReg;
113 private ServiceRegistration<UserAdmin> userAdminReg;
114
115 /*
116 * SERVICES IMPLEMENTATIONS
117 */
118 private NodeLogger logger;
119 private BitronixTransactionManager transactionManager;
120 private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry;
121 private OsgiJackrabbitRepositoryFactory repositoryFactory;
122 JackrabbitRepository repository;
123 private NodeUserAdmin userAdmin;
124
125 // Members
126 private final static Log log = LogFactory.getLog(Kernel.class);
127 ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName());
128 private final BundleContext bc = Activator.getBundleContext();
129 private final NodeSecurity nodeSecurity;
130 private DataHttp dataHttp;
131 private NodeHttp nodeHttp;
132 private KernelThread kernelThread;
133
134 private Locale defaultLocale = null;
135 private List<Locale> locales = null;
136
137 public Kernel() {
138 // KernelUtils.logFrameworkProperties(log);
139 nodeSecurity = new NodeSecurity();
140 }
141
142 final void init() {
143 Subject.doAs(nodeSecurity.getKernelSubject(), new PrivilegedAction<Void>() {
144 @Override
145 public Void run() {
146 doInit();
147 return null;
148 }
149 });
150 }
151
152 private void doInit() {
153 long begin = System.currentTimeMillis();
154 // Use CMS bundle classloader
155 ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
156 Thread.currentThread().setContextClassLoader(Kernel.class.getClassLoader());
157 try {
158 // Listen to service publication (also ours)
159 bc.addServiceListener(Kernel.this);
160
161 if (nodeSecurity.isFirstInit())
162 firstInit();
163
164 defaultLocale = new Locale(getFrameworkProp(I18N_DEFAULT_LOCALE, ENGLISH.getLanguage()));
165 locales = asLocaleList(getFrameworkProp(I18N_LOCALES));
166
167 ServiceTracker<LogReaderService, LogReaderService> logReaderService = new ServiceTracker<LogReaderService, LogReaderService>(
168 bc, LogReaderService.class, null);
169 logReaderService.open();
170 logger = new NodeLogger(logReaderService.getService());
171 logReaderService.close();
172
173 if (isMaintenance())
174 maintenanceInit();
175 else
176 normalInit();
177 } catch (Exception e) {
178 log.error("Cannot initialize Argeo CMS", e);
179 throw new ArgeoException("Cannot initialize", e);
180 } finally {
181 Thread.currentThread().setContextClassLoader(currentContextCl);
182 // FIXME better manage lifecycle.
183 try {
184 new LoginContext(LOGIN_CONTEXT_KERNEL, nodeSecurity.getKernelSubject()).logout();
185 } catch (LoginException e) {
186 e.printStackTrace();
187 }
188 }
189
190 long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
191 log.info("## ARGEO CMS UP in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s ##");
192 long initDuration = System.currentTimeMillis() - begin;
193 if (log.isTraceEnabled())
194 log.trace("Kernel initialization took " + initDuration + "ms");
195 directorsCut(initDuration);
196 }
197
198 private void normalInit() {
199 ConfigurationAdmin conf = findConfigurationAdmin();
200
201 // HTTP
202 initWebServer(conf);
203 // ServiceReference<ExtendedHttpService> sr =
204 // bc.getServiceReference(ExtendedHttpService.class);
205 // if (sr != null)
206 // addHttpService(sr);
207 // else
208 // log.warn("No http service found");
209
210 // Initialise services
211 initTransactionManager();
212
213 try {
214 Configuration nodeConf = conf.getConfiguration(ArgeoJcrConstants.REPO_PID_NODE);
215 if (nodeConf.getProperties() == null) {
216 Dictionary<String, ?> props = getNodeConfigFromFrameworkProperties();
217 if (props == null) {
218 // TODO interactive configuration
219 if (log.isDebugEnabled())
220 log.debug("No argeo.node.repo.type=localfs|h2|postgresql|memory"
221 + " property defined, entering interactive mode...");
222 return;
223 }
224 nodeConf.update(props);
225 }
226 } catch (IOException e) {
227 throw new CmsException("Cannot get configuration", e);
228 }
229
230 ManagedJackrabbitRepository nodeRepo = new ManagedJackrabbitRepository();
231 String[] clazzes = { ManagedService.class.getName(), Repository.class.getName(),
232 JackrabbitRepository.class.getName() };
233 Hashtable<String, String> serviceProps = new Hashtable<String, String>();
234 serviceProps.put(Constants.SERVICE_PID, ArgeoJcrConstants.REPO_PID_NODE);
235 serviceProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, ArgeoJcrConstants.ALIAS_NODE);
236 ServiceRegistration<?> nodeSr = bc.registerService(clazzes, nodeRepo, serviceProps);
237 nodeRepo.waitForInit();
238
239 new JackrabbitDataModel(bc).prepareDataModel(nodeRepo);
240 prepareDataModel(nodeRepo);
241
242 repository = (JackrabbitRepository) bc.getService(nodeSr.getReference());
243
244 if (repository == null)
245 repository = new NodeRepository();
246 if (repositoryFactory == null) {
247 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
248 repositoryFactory.setBundleContext(bc);
249 }
250 userAdmin = new NodeUserAdmin(transactionManager, repository);
251
252 // ADMIN UIs
253 UserUi userUi = new UserUi();
254 Hashtable<String, String> props = new Hashtable<String, String>();
255 props.put("contextName", "user");
256 bc.registerService(ApplicationConfiguration.class, userUi, props);
257
258 // Kernel thread
259 kernelThread = new KernelThread(this);
260 kernelThread.setContextClassLoader(Kernel.class.getClassLoader());
261 kernelThread.start();
262
263 // Publish services to OSGi
264 publish();
265 }
266
267 private Dictionary<String, ?> getNodeConfigFromFrameworkProperties() {
268 String repoType = KernelUtils.getFrameworkProp(KernelConstants.NODE_REPO_PROP_PREFIX + RepoConf.type.name());
269 if (repoType == null)
270 return null;
271
272 Hashtable<String, Object> props = new Hashtable<String, Object>();
273 for (RepoConf repoConf : RepoConf.values()) {
274 String value = KernelUtils.getFrameworkProp(KernelConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
275 if (value != null)
276 props.put(repoConf.name(), value);
277 }
278 return props;
279 }
280
281 private final static String CMS_DATA_MODEL = "cms.datamodel";
282
283 private void prepareDataModel(ManagedJackrabbitRepository nodeRepo) {
284 Session adminSession = null;
285 try {
286 Set<String> processed = new HashSet<String>();
287 adminSession = nodeRepo.login();
288 bundles: for (Bundle bundle : bc.getBundles()) {
289 BundleWiring wiring = bundle.adapt(BundleWiring.class);
290 if (wiring == null) {
291 if (log.isTraceEnabled())
292 log.error("No wiring for " + bundle.getSymbolicName());
293 continue bundles;
294 }
295 processWiring(adminSession, wiring, processed);
296 }
297 } catch (RepositoryException e) {
298 throw new CmsException("Cannot prepare data model", e);
299 } finally {
300 JcrUtils.logoutQuietly(adminSession);
301 }
302 }
303
304 private void processWiring(Session adminSession, BundleWiring wiring, Set<String> processed) {
305 // recursively process requirements first
306 List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL);
307 for (BundleWire wire : requiredWires) {
308 processWiring(adminSession, wire.getProviderWiring(), processed);
309 // registerCnd(adminSession, wire.getCapability(), processed);
310 }
311 List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL);
312 for (BundleCapability capability : capabilities) {
313 registerCnd(adminSession, capability, processed);
314 }
315 }
316
317 private void registerCnd(Session adminSession, BundleCapability capability, Set<String> processed) {
318 Map<String, Object> attrs = capability.getAttributes();
319 String name = attrs.get("name").toString();
320 if (processed.contains(name)) {
321 if (log.isTraceEnabled())
322 log.trace("Data model " + name + " has already been processed");
323 return;
324 }
325 String path = attrs.get("cnd").toString();
326 URL url = capability.getRevision().getBundle().getResource(path);
327 try (Reader reader = new InputStreamReader(url.openStream())) {
328 CndImporter.registerNodeTypes(reader, adminSession, true);
329 processed.add(name);
330 if (log.isDebugEnabled())
331 log.debug("Registered CND " + url);
332 } catch (Exception e) {
333 throw new CmsException("Cannot read cnd " + url, e);
334 }
335
336 Hashtable<String, Object> properties = new Hashtable<>();
337 properties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, name);
338 bc.registerService(Repository.class, adminSession.getRepository(), properties);
339 if (log.isDebugEnabled())
340 log.debug("Published data model " + name);
341 }
342
343 private boolean isMaintenance() {
344 String startLevel = KernelUtils.getFrameworkProp("osgi.startLevel");
345 if (startLevel == null)
346 return false;
347 int bundleStartLevel = bc.getBundle().adapt(BundleStartLevel.class).getStartLevel();
348 // int frameworkStartLevel =
349 // bc.getBundle(0).adapt(BundleStartLevel.class)
350 // .getStartLevel();
351 int frameworkStartLevel = Integer.parseInt(startLevel);
352 // int frameworkStartLevel = bc.getBundle(0)
353 // .adapt(FrameworkStartLevel.class).getStartLevel();
354 return bundleStartLevel == frameworkStartLevel;
355 }
356
357 private void maintenanceInit() {
358 log.info("## MAINTENANCE ##");
359 bc.addServiceListener(Kernel.this);
360 initWebServer(null);
361 MaintenanceUi maintenanceUi = new MaintenanceUi();
362 Hashtable<String, String> props = new Hashtable<String, String>();
363 props.put("contextName", "maintenance");
364 bc.registerService(ApplicationConfiguration.class, maintenanceUi, props);
365 }
366
367 private void firstInit() {
368 log.info("## FIRST INIT ##");
369 String nodeInit = getFrameworkProp(NODE_INIT);
370 if (nodeInit == null)
371 nodeInit = "../../init";
372 if (nodeInit.startsWith("http")) {
373 remoteFirstInit(nodeInit);
374 return;
375 }
376 File initDir;
377 if (nodeInit.startsWith("."))
378 initDir = KernelUtils.getExecutionDir(nodeInit);
379 else
380 initDir = new File(nodeInit);
381 // TODO also uncompress archives
382 if (initDir.exists())
383 try {
384 FileUtils.copyDirectory(initDir, getOsgiInstanceDir(), new FileFilter() {
385
386 @Override
387 public boolean accept(File pathname) {
388 if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
389 return false;
390 return true;
391 }
392 });
393 log.info("CMS initialized from " + initDir.getCanonicalPath());
394 } catch (IOException e) {
395 throw new CmsException("Cannot initialize from " + initDir, e);
396 }
397 }
398
399 private void remoteFirstInit(String uri) {
400 try {
401 repository = new NodeRepository();
402 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
403 Repository remoteRepository = ArgeoJcrUtils.getRepositoryByUri(repositoryFactory, uri);
404 Session remoteSession = remoteRepository.login(new SimpleCredentials("root", "demo".toCharArray()), "main");
405 Session localSession = this.repository.login();
406 // FIXME register node type
407 // if (false)
408 // CndImporter.registerNodeTypes(null, localSession);
409 ByteArrayOutputStream out = new ByteArrayOutputStream();
410 remoteSession.exportSystemView("/", out, true, false);
411 ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
412 localSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
413 // JcrUtils.copy(remoteSession.getRootNode(),
414 // localSession.getRootNode());
415 } catch (Exception e) {
416 throw new CmsException("Cannot first init from " + uri, e);
417 }
418 }
419
420 /** Can be null */
421 private ConfigurationAdmin findConfigurationAdmin() {
422 configurationAdmin = bc.getServiceReference(ConfigurationAdmin.class);
423 if (configurationAdmin == null) {
424 return null;
425 }
426 return bc.getService(configurationAdmin);
427 }
428
429 private void initTransactionManager() {
430 bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
431 tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID));
432
433 // File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
434 // getOsgiInstancePath(DIR_TRANSACTIONS)));
435 File tmBaseDir = bc.getDataFile(DIR_TRANSACTIONS);
436 File tmDir1 = new File(tmBaseDir, "btm1");
437 tmDir1.mkdirs();
438 tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath());
439 File tmDir2 = new File(tmBaseDir, "btm2");
440 tmDir2.mkdirs();
441 tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath());
442 transactionManager = getTransactionManager();
443 transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
444 }
445
446 private void initWebServer(ConfigurationAdmin conf) {
447 String httpPort = getFrameworkProp("org.osgi.service.http.port");
448 String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
449 try {
450 if (httpPort != null || httpsPort != null) {
451 Hashtable<String, Object> jettyProps = new Hashtable<String, Object>();
452 if (httpPort != null) {
453 jettyProps.put(JettyConstants.HTTP_PORT, httpPort);
454 jettyProps.put(JettyConstants.HTTP_ENABLED, true);
455 }
456 if (httpsPort != null) {
457 jettyProps.put(JettyConstants.HTTPS_PORT, httpsPort);
458 jettyProps.put(JettyConstants.HTTPS_ENABLED, true);
459 jettyProps.put(JettyConstants.SSL_KEYSTORETYPE, "PKCS12");
460 jettyProps.put(JettyConstants.SSL_KEYSTORE,
461 nodeSecurity.getHttpServerKeyStore().getCanonicalPath());
462 jettyProps.put(JettyConstants.SSL_PASSWORD, "changeit");
463 jettyProps.put(JettyConstants.SSL_WANTCLIENTAUTH, true);
464 }
465 if (conf != null) {
466 // TODO make filter more generic
467 String filter = "(" + JettyConstants.HTTP_PORT + "=" + httpPort + ")";
468 if (conf.listConfigurations(filter) != null)
469 return;
470 Configuration jettyConf = conf.createFactoryConfiguration(JETTY_FACTORY_PID, null);
471 jettyConf.update(jettyProps);
472 } else {
473 JettyConfigurator.startServer("default", jettyProps);
474 }
475 }
476 } catch (Exception e) {
477 throw new CmsException("Cannot initialize web server on " + httpPortsMsg(httpPort, httpsPort), e);
478 }
479 }
480
481 @SuppressWarnings("unchecked")
482 private void publish() {
483
484 // Logging
485 loggerReg = bc.registerService(ArgeoLogger.class, logger, null);
486 // Transaction
487 tmReg = bc.registerService(TransactionManager.class, transactionManager, null);
488 utReg = bc.registerService(UserTransaction.class, transactionManager, null);
489 tsrReg = bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null);
490 // User admin
491 userAdminReg = bc.registerService(UserAdmin.class, userAdmin, userAdmin.currentState());
492 // JCR
493 Hashtable<String, String> regProps = new Hashtable<String, String>();
494 regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE);
495 repositoryReg = (ServiceRegistration<? extends Repository>) bc.registerService(
496 new String[] { Repository.class.getName(), JackrabbitRepository.class.getName() }, repository,
497 regProps);
498 repositoryFactoryReg = bc.registerService(RepositoryFactory.class, repositoryFactory, null);
499 }
500
501 void destroy() {
502 long begin = System.currentTimeMillis();
503 unpublish();
504
505 kernelThread.destroyAndJoin();
506
507 if (dataHttp != null)
508 dataHttp.destroy();
509 if (nodeHttp != null)
510 nodeHttp.destroy();
511 if (userAdmin != null)
512 userAdmin.destroy();
513 if (repository != null)
514 repository.shutdown();
515 if (transactionManager != null)
516 transactionManager.shutdown();
517
518 bc.removeServiceListener(this);
519
520 // Clean hanging threads from Jackrabbit
521 TransientFileFactory.shutdown();
522
523 // Clean hanging Gogo shell thread
524 new GogoShellKiller().start();
525
526 nodeSecurity.destroy();
527 long duration = System.currentTimeMillis() - begin;
528 log.info("## ARGEO CMS DOWN in " + (duration / 1000) + "." + (duration % 1000) + "s ##");
529 }
530
531 private void unpublish() {
532 userAdminReg.unregister();
533 repositoryFactoryReg.unregister();
534 repositoryReg.unregister();
535 tmReg.unregister();
536 utReg.unregister();
537 tsrReg.unregister();
538 loggerReg.unregister();
539 }
540
541 @Override
542 public void serviceChanged(ServiceEvent event) {
543 ServiceReference<?> sr = event.getServiceReference();
544 Object service = bc.getService(sr);
545 if (service instanceof Repository) {
546 Object jcrRepoAlias = sr.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
547 if (jcrRepoAlias != null) {// JCR repository
548 String alias = jcrRepoAlias.toString();
549 Repository repository = (Repository) bc.getService(sr);
550 Map<String, Object> props = new HashMap<String, Object>();
551 for (String key : sr.getPropertyKeys())
552 props.put(key, sr.getProperty(key));
553 if (ServiceEvent.REGISTERED == event.getType()) {
554 try {
555 // repositoryFactory.register(repository, props);
556 dataHttp.registerRepositoryServlets(alias, repository);
557 } catch (Exception e) {
558 throw new CmsException("Could not publish JCR repository " + alias, e);
559 }
560 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
561 // repositoryFactory.unregister(repository, props);
562 dataHttp.unregisterRepositoryServlets(alias);
563 }
564 }
565 } else if (service instanceof ExtendedHttpService) {
566 if (ServiceEvent.REGISTERED == event.getType()) {
567 addHttpService(sr);
568 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
569 dataHttp.destroy();
570 dataHttp = null;
571 }
572 }
573 }
574
575 private void addHttpService(ServiceReference<?> sr) {
576 // for (String key : sr.getPropertyKeys())
577 // log.debug(key + "=" + sr.getProperty(key));
578 ExtendedHttpService httpService = (ExtendedHttpService) bc.getService(sr);
579 // TODO find constants
580 Object httpPort = sr.getProperty("http.port");
581 Object httpsPort = sr.getProperty("https.port");
582 dataHttp = new DataHttp(httpService);
583 nodeHttp = new NodeHttp(httpService, bc);
584 if (log.isDebugEnabled())
585 log.debug(httpPortsMsg(httpPort, httpsPort));
586 }
587
588 private String httpPortsMsg(Object httpPort, Object httpsPort) {
589 return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : "");
590 }
591
592 @Override
593 public Locale getDefaultLocale() {
594 return defaultLocale;
595 }
596
597 /** Can be null. */
598 @Override
599 public List<Locale> getLocales() {
600 return locales;
601 }
602
603 final private static void directorsCut(long initDuration) {
604 // final long ms = 128l + (long) (Math.random() * 128d);
605 long ms = initDuration / 100;
606 log.info("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
607 long beginNano = System.nanoTime();
608 try {
609 Thread.sleep(ms, 0);
610 } catch (InterruptedException e) {
611 // silent
612 }
613 long durationNano = System.nanoTime() - beginNano;
614 final double M = 1000d * 1000d;
615 double sleepAccuracy = ((double) durationNano) / (ms * M);
616 if (log.isDebugEnabled())
617 log.debug("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
618 }
619
620 /** Workaround for blocking Gogo shell by system shutdown. */
621 private class GogoShellKiller extends Thread {
622
623 public GogoShellKiller() {
624 super("Gogo shell killer");
625 setDaemon(true);
626 }
627
628 @Override
629 public void run() {
630 ThreadGroup rootTg = getRootThreadGroup(null);
631 Thread gogoShellThread = findGogoShellThread(rootTg);
632 if (gogoShellThread == null)
633 return;
634 while (getNonDaemonCount(rootTg) > 2) {
635 try {
636 Thread.sleep(100);
637 } catch (InterruptedException e) {
638 // silent
639 }
640 }
641 gogoShellThread = findGogoShellThread(rootTg);
642 if (gogoShellThread == null)
643 return;
644 System.exit(0);
645 }
646 }
647
648 private static ThreadGroup getRootThreadGroup(ThreadGroup tg) {
649 if (tg == null)
650 tg = Thread.currentThread().getThreadGroup();
651 if (tg.getParent() == null)
652 return tg;
653 else
654 return getRootThreadGroup(tg.getParent());
655 }
656
657 private static int getNonDaemonCount(ThreadGroup rootThreadGroup) {
658 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
659 rootThreadGroup.enumerate(threads);
660 int nonDameonCount = 0;
661 for (Thread t : threads)
662 if (t != null && !t.isDaemon())
663 nonDameonCount++;
664 return nonDameonCount;
665 }
666
667 private static Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
668 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
669 rootThreadGroup.enumerate(threads, true);
670 for (Thread thread : threads) {
671 if (thread.getName().equals("Gogo shell"))
672 return thread;
673 }
674 return null;
675 }
676
677 }