]> git.argeo.org Git - lgpl/argeo-commons.git/blob - kernel/Kernel.java
Prepare next development cycle
[lgpl/argeo-commons.git] / kernel / 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)// TODO interactive configuration
218 return;
219 nodeConf.update(props);
220 }
221 } catch (IOException e) {
222 throw new CmsException("Cannot get configuration", e);
223 }
224
225 ManagedJackrabbitRepository nodeRepo = new ManagedJackrabbitRepository();
226 String[] clazzes = { ManagedService.class.getName(), Repository.class.getName(),
227 JackrabbitRepository.class.getName() };
228 Hashtable<String, String> serviceProps = new Hashtable<String, String>();
229 serviceProps.put(Constants.SERVICE_PID, ArgeoJcrConstants.REPO_PID_NODE);
230 serviceProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, ArgeoJcrConstants.ALIAS_NODE);
231 ServiceRegistration<?> nodeSr = bc.registerService(clazzes, nodeRepo, serviceProps);
232 nodeRepo.waitForInit();
233
234 new JackrabbitDataModel(bc).prepareDataModel(nodeRepo);
235 prepareDataModel(nodeRepo);
236
237 repository = (JackrabbitRepository) bc.getService(nodeSr.getReference());
238
239 if (repository == null)
240 repository = new NodeRepository();
241 if (repositoryFactory == null)
242 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
243 userAdmin = new NodeUserAdmin(transactionManager, repository);
244
245 // ADMIN UIs
246 UserUi userUi = new UserUi();
247 Hashtable<String, String> props = new Hashtable<String, String>();
248 props.put("contextName", "user");
249 bc.registerService(ApplicationConfiguration.class, userUi, props);
250
251 // Kernel thread
252 kernelThread = new KernelThread(this);
253 kernelThread.setContextClassLoader(Kernel.class.getClassLoader());
254 kernelThread.start();
255
256 // Publish services to OSGi
257 publish();
258 }
259
260 private Dictionary<String, ?> getNodeConfigFromFrameworkProperties() {
261 String repoType = KernelUtils.getFrameworkProp(KernelConstants.NODE_REPO_PROP_PREFIX + RepoConf.type.name());
262 if (repoType == null)
263 return null;
264
265 Hashtable<String, Object> props = new Hashtable<String, Object>();
266 for (RepoConf repoConf : RepoConf.values()) {
267 String value = KernelUtils.getFrameworkProp(KernelConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
268 if (value != null)
269 props.put(repoConf.name(), value);
270 }
271 return props;
272 }
273
274 private final static String CMS_DATA_MODEL = "cms.datamodel";
275
276 private void prepareDataModel(ManagedJackrabbitRepository nodeRepo) {
277 Session adminSession = null;
278 try {
279 Set<String> processed = new HashSet<String>();
280 adminSession = nodeRepo.login();
281 bundles: for (Bundle bundle : bc.getBundles()) {
282 BundleWiring wiring = bundle.adapt(BundleWiring.class);
283 if (wiring == null) {
284 if (log.isTraceEnabled())
285 log.error("No wiring for " + bundle.getSymbolicName());
286 continue bundles;
287 }
288 processWiring(adminSession, wiring, processed);
289 }
290 } catch (RepositoryException e) {
291 throw new CmsException("Cannot prepare data model", e);
292 } finally {
293 JcrUtils.logoutQuietly(adminSession);
294 }
295 }
296
297 private void processWiring(Session adminSession, BundleWiring wiring, Set<String> processed) {
298 // recursively process requirements first
299 List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL);
300 for (BundleWire wire : requiredWires) {
301 processWiring(adminSession, wire.getProviderWiring(), processed);
302 // registerCnd(adminSession, wire.getCapability(), processed);
303 }
304 List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL);
305 for (BundleCapability capability : capabilities) {
306 registerCnd(adminSession, capability, processed);
307 }
308 }
309
310 private void registerCnd(Session adminSession, BundleCapability capability, Set<String> processed) {
311 Map<String, Object> attrs = capability.getAttributes();
312 String name = attrs.get("name").toString();
313 if (processed.contains(name)) {
314 if (log.isTraceEnabled())
315 log.trace("Data model " + name + " has already been processed");
316 return;
317 }
318 String path = attrs.get("cnd").toString();
319 URL url = capability.getRevision().getBundle().getResource(path);
320 try (Reader reader = new InputStreamReader(url.openStream())) {
321 CndImporter.registerNodeTypes(reader, adminSession, true);
322 processed.add(name);
323 if (log.isDebugEnabled())
324 log.debug("Registered CND " + url);
325 } catch (Exception e) {
326 throw new CmsException("Cannot read cnd " + url, e);
327 }
328
329 Hashtable<String, Object> properties = new Hashtable<>();
330 properties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, name);
331 bc.registerService(Repository.class, adminSession.getRepository(), properties);
332 if (log.isDebugEnabled())
333 log.debug("Published data model " + name);
334 }
335
336 private boolean isMaintenance() {
337 String startLevel = KernelUtils.getFrameworkProp("osgi.startLevel");
338 if (startLevel == null)
339 return false;
340 int bundleStartLevel = bc.getBundle().adapt(BundleStartLevel.class).getStartLevel();
341 // int frameworkStartLevel =
342 // bc.getBundle(0).adapt(BundleStartLevel.class)
343 // .getStartLevel();
344 int frameworkStartLevel = Integer.parseInt(startLevel);
345 // int frameworkStartLevel = bc.getBundle(0)
346 // .adapt(FrameworkStartLevel.class).getStartLevel();
347 return bundleStartLevel == frameworkStartLevel;
348 }
349
350 private void maintenanceInit() {
351 log.info("## MAINTENANCE ##");
352 bc.addServiceListener(Kernel.this);
353 initWebServer(null);
354 MaintenanceUi maintenanceUi = new MaintenanceUi();
355 Hashtable<String, String> props = new Hashtable<String, String>();
356 props.put("contextName", "maintenance");
357 bc.registerService(ApplicationConfiguration.class, maintenanceUi, props);
358 }
359
360 private void firstInit() {
361 log.info("## FIRST INIT ##");
362 String nodeInit = getFrameworkProp(NODE_INIT);
363 if (nodeInit == null)
364 nodeInit = "../../init";
365 if (nodeInit.startsWith("http")) {
366 remoteFirstInit(nodeInit);
367 return;
368 }
369 File initDir;
370 if (nodeInit.startsWith("."))
371 initDir = KernelUtils.getExecutionDir(nodeInit);
372 else
373 initDir = new File(nodeInit);
374 // TODO also uncompress archives
375 if (initDir.exists())
376 try {
377 FileUtils.copyDirectory(initDir, getOsgiInstanceDir(), new FileFilter() {
378
379 @Override
380 public boolean accept(File pathname) {
381 if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
382 return false;
383 return true;
384 }
385 });
386 log.info("CMS initialized from " + initDir.getCanonicalPath());
387 } catch (IOException e) {
388 throw new CmsException("Cannot initialize from " + initDir, e);
389 }
390 }
391
392 private void remoteFirstInit(String uri) {
393 try {
394 repository = new NodeRepository();
395 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
396 Repository remoteRepository = ArgeoJcrUtils.getRepositoryByUri(repositoryFactory, uri);
397 Session remoteSession = remoteRepository.login(new SimpleCredentials("root", "demo".toCharArray()), "main");
398 Session localSession = this.repository.login();
399 // FIXME register node type
400 // if (false)
401 // CndImporter.registerNodeTypes(null, localSession);
402 ByteArrayOutputStream out = new ByteArrayOutputStream();
403 remoteSession.exportSystemView("/", out, true, false);
404 ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
405 localSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
406 // JcrUtils.copy(remoteSession.getRootNode(),
407 // localSession.getRootNode());
408 } catch (Exception e) {
409 throw new CmsException("Cannot first init from " + uri, e);
410 }
411 }
412
413 /** Can be null */
414 private ConfigurationAdmin findConfigurationAdmin() {
415 configurationAdmin = bc.getServiceReference(ConfigurationAdmin.class);
416 if (configurationAdmin == null) {
417 return null;
418 }
419 return bc.getService(configurationAdmin);
420 }
421
422 private void initTransactionManager() {
423 bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
424 tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID));
425
426 // File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
427 // getOsgiInstancePath(DIR_TRANSACTIONS)));
428 File tmBaseDir = bc.getDataFile(DIR_TRANSACTIONS);
429 File tmDir1 = new File(tmBaseDir, "btm1");
430 tmDir1.mkdirs();
431 tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath());
432 File tmDir2 = new File(tmBaseDir, "btm2");
433 tmDir2.mkdirs();
434 tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath());
435 transactionManager = getTransactionManager();
436 transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
437 }
438
439 private void initWebServer(ConfigurationAdmin conf) {
440 String httpPort = getFrameworkProp("org.osgi.service.http.port");
441 String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
442 try {
443 if (httpPort != null || httpsPort != null) {
444 Hashtable<String, Object> jettyProps = new Hashtable<String, Object>();
445 if (httpPort != null) {
446 jettyProps.put(JettyConstants.HTTP_PORT, httpPort);
447 jettyProps.put(JettyConstants.HTTP_ENABLED, true);
448 }
449 if (httpsPort != null) {
450 jettyProps.put(JettyConstants.HTTPS_PORT, httpsPort);
451 jettyProps.put(JettyConstants.HTTPS_ENABLED, true);
452 jettyProps.put(JettyConstants.SSL_KEYSTORETYPE, "PKCS12");
453 jettyProps.put(JettyConstants.SSL_KEYSTORE,
454 nodeSecurity.getHttpServerKeyStore().getCanonicalPath());
455 jettyProps.put(JettyConstants.SSL_PASSWORD, "changeit");
456 jettyProps.put(JettyConstants.SSL_WANTCLIENTAUTH, true);
457 }
458 if (conf != null) {
459 // TODO make filter more generic
460 String filter = "(" + JettyConstants.HTTP_PORT + "=" + httpPort + ")";
461 if (conf.listConfigurations(filter) != null)
462 return;
463 Configuration jettyConf = conf.createFactoryConfiguration(JETTY_FACTORY_PID, null);
464 jettyConf.update(jettyProps);
465 } else {
466 JettyConfigurator.startServer("default", jettyProps);
467 }
468 }
469 } catch (Exception e) {
470 throw new CmsException("Cannot initialize web server on " + httpPortsMsg(httpPort, httpsPort), e);
471 }
472 }
473
474 @SuppressWarnings("unchecked")
475 private void publish() {
476
477 // Logging
478 loggerReg = bc.registerService(ArgeoLogger.class, logger, null);
479 // Transaction
480 tmReg = bc.registerService(TransactionManager.class, transactionManager, null);
481 utReg = bc.registerService(UserTransaction.class, transactionManager, null);
482 tsrReg = bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null);
483 // User admin
484 userAdminReg = bc.registerService(UserAdmin.class, userAdmin, userAdmin.currentState());
485 // JCR
486 Hashtable<String, String> regProps = new Hashtable<String, String>();
487 regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE);
488 repositoryReg = (ServiceRegistration<? extends Repository>) bc.registerService(
489 new String[] { Repository.class.getName(), JackrabbitRepository.class.getName() }, repository,
490 regProps);
491 repositoryFactoryReg = bc.registerService(RepositoryFactory.class, repositoryFactory, null);
492 }
493
494 void destroy() {
495 long begin = System.currentTimeMillis();
496 unpublish();
497
498 kernelThread.destroyAndJoin();
499
500 if (dataHttp != null)
501 dataHttp.destroy();
502 if (nodeHttp != null)
503 nodeHttp.destroy();
504 if (userAdmin != null)
505 userAdmin.destroy();
506 if (repository != null)
507 repository.shutdown();
508 if (transactionManager != null)
509 transactionManager.shutdown();
510
511 bc.removeServiceListener(this);
512
513 // Clean hanging threads from Jackrabbit
514 TransientFileFactory.shutdown();
515
516 // Clean hanging Gogo shell thread
517 new GogoShellKiller().start();
518
519 nodeSecurity.destroy();
520 long duration = System.currentTimeMillis() - begin;
521 log.info("## ARGEO CMS DOWN in " + (duration / 1000) + "." + (duration % 1000) + "s ##");
522 }
523
524 private void unpublish() {
525 userAdminReg.unregister();
526 repositoryFactoryReg.unregister();
527 repositoryReg.unregister();
528 tmReg.unregister();
529 utReg.unregister();
530 tsrReg.unregister();
531 loggerReg.unregister();
532 }
533
534 @Override
535 public void serviceChanged(ServiceEvent event) {
536 ServiceReference<?> sr = event.getServiceReference();
537 Object service = bc.getService(sr);
538 if (service instanceof Repository) {
539 Object jcrRepoAlias = sr.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
540 if (jcrRepoAlias != null) {// JCR repository
541 String alias = jcrRepoAlias.toString();
542 Repository repository = (Repository) bc.getService(sr);
543 Map<String, Object> props = new HashMap<String, Object>();
544 for (String key : sr.getPropertyKeys())
545 props.put(key, sr.getProperty(key));
546 if (ServiceEvent.REGISTERED == event.getType()) {
547 try {
548 repositoryFactory.register(repository, props);
549 dataHttp.registerRepositoryServlets(alias, repository);
550 } catch (Exception e) {
551 throw new CmsException("Could not publish JCR repository " + alias, e);
552 }
553 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
554 repositoryFactory.unregister(repository, props);
555 dataHttp.unregisterRepositoryServlets(alias);
556 }
557 }
558 } else if (service instanceof ExtendedHttpService) {
559 if (ServiceEvent.REGISTERED == event.getType()) {
560 addHttpService(sr);
561 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
562 dataHttp.destroy();
563 dataHttp = null;
564 }
565 }
566 }
567
568 private void addHttpService(ServiceReference<?> sr) {
569 // for (String key : sr.getPropertyKeys())
570 // log.debug(key + "=" + sr.getProperty(key));
571 ExtendedHttpService httpService = (ExtendedHttpService) bc.getService(sr);
572 // TODO find constants
573 Object httpPort = sr.getProperty("http.port");
574 Object httpsPort = sr.getProperty("https.port");
575 dataHttp = new DataHttp(httpService);
576 nodeHttp = new NodeHttp(httpService, bc);
577 if (log.isDebugEnabled())
578 log.debug(httpPortsMsg(httpPort, httpsPort));
579 }
580
581 private String httpPortsMsg(Object httpPort, Object httpsPort) {
582 return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : "");
583 }
584
585 @Override
586 public Locale getDefaultLocale() {
587 return defaultLocale;
588 }
589
590 /** Can be null. */
591 @Override
592 public List<Locale> getLocales() {
593 return locales;
594 }
595
596 final private static void directorsCut(long initDuration) {
597 // final long ms = 128l + (long) (Math.random() * 128d);
598 long ms = initDuration / 100;
599 log.info("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
600 long beginNano = System.nanoTime();
601 try {
602 Thread.sleep(ms, 0);
603 } catch (InterruptedException e) {
604 // silent
605 }
606 long durationNano = System.nanoTime() - beginNano;
607 final double M = 1000d * 1000d;
608 double sleepAccuracy = ((double) durationNano) / (ms * M);
609 if (log.isDebugEnabled())
610 log.debug("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
611 }
612
613 /** Workaround for blocking Gogo shell by system shutdown. */
614 private class GogoShellKiller extends Thread {
615
616 public GogoShellKiller() {
617 super("Gogo shell killer");
618 setDaemon(true);
619 }
620
621 @Override
622 public void run() {
623 ThreadGroup rootTg = getRootThreadGroup(null);
624 Thread gogoShellThread = findGogoShellThread(rootTg);
625 if (gogoShellThread == null)
626 return;
627 while (getNonDaemonCount(rootTg) > 2) {
628 try {
629 Thread.sleep(100);
630 } catch (InterruptedException e) {
631 // silent
632 }
633 }
634 gogoShellThread = findGogoShellThread(rootTg);
635 if (gogoShellThread == null)
636 return;
637 System.exit(0);
638 }
639 }
640
641 private static ThreadGroup getRootThreadGroup(ThreadGroup tg) {
642 if (tg == null)
643 tg = Thread.currentThread().getThreadGroup();
644 if (tg.getParent() == null)
645 return tg;
646 else
647 return getRootThreadGroup(tg.getParent());
648 }
649
650 private static int getNonDaemonCount(ThreadGroup rootThreadGroup) {
651 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
652 rootThreadGroup.enumerate(threads);
653 int nonDameonCount = 0;
654 for (Thread t : threads)
655 if (t != null && !t.isDaemon())
656 nonDameonCount++;
657 return nonDameonCount;
658 }
659
660 private static Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
661 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
662 rootThreadGroup.enumerate(threads, true);
663 for (Thread thread : threads) {
664 if (thread.getName().equals("Gogo shell"))
665 return thread;
666 }
667 return null;
668 }
669
670 }