]> 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.lang.management.ManagementFactory;
19 import java.security.PrivilegedAction;
20 import java.util.HashMap;
21 import java.util.Hashtable;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import javax.jcr.ImportUUIDBehavior;
27 import javax.jcr.Repository;
28 import javax.jcr.RepositoryFactory;
29 import javax.jcr.Session;
30 import javax.jcr.SimpleCredentials;
31 import javax.security.auth.Subject;
32 import javax.transaction.TransactionManager;
33 import javax.transaction.TransactionSynchronizationRegistry;
34 import javax.transaction.UserTransaction;
35
36 import org.apache.commons.io.FileUtils;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.jackrabbit.api.JackrabbitRepository;
40 import org.apache.jackrabbit.util.TransientFileFactory;
41 import org.argeo.ArgeoException;
42 import org.argeo.ArgeoLogger;
43 import org.argeo.cms.CmsException;
44 import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory;
45 import org.argeo.jcr.ArgeoJcrConstants;
46 import org.argeo.jcr.ArgeoJcrUtils;
47 import org.eclipse.equinox.http.jetty.JettyConfigurator;
48 import org.eclipse.equinox.http.jetty.JettyConstants;
49 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
50 import org.eclipse.rap.rwt.application.ApplicationConfiguration;
51 import org.osgi.framework.BundleContext;
52 import org.osgi.framework.ServiceEvent;
53 import org.osgi.framework.ServiceListener;
54 import org.osgi.framework.ServiceReference;
55 import org.osgi.framework.ServiceRegistration;
56 import org.osgi.service.cm.Configuration;
57 import org.osgi.service.cm.ConfigurationAdmin;
58 import org.osgi.service.log.LogReaderService;
59 import org.osgi.service.useradmin.UserAdmin;
60 import org.osgi.util.tracker.ServiceTracker;
61
62 import bitronix.tm.BitronixTransactionManager;
63 import bitronix.tm.BitronixTransactionSynchronizationRegistry;
64 import bitronix.tm.TransactionManagerServices;
65
66 /**
67 * Argeo CMS Kernel. Responsible for :
68 * <ul>
69 * <li>security</li>
70 * <li>provisioning</li>
71 * <li>transaction</li>
72 * <li>logging</li>
73 * <li>local and remote file systems access</li>
74 * <li>OS access</li>
75 * </ul>
76 */
77 final class Kernel implements KernelHeader, KernelConstants, ServiceListener {
78 /*
79 * SERVICE REFERENCES
80 */
81 private ServiceReference<ConfigurationAdmin> configurationAdmin;
82 /*
83 * REGISTERED SERVICES
84 */
85 private ServiceRegistration<ArgeoLogger> loggerReg;
86 private ServiceRegistration<TransactionManager> tmReg;
87 private ServiceRegistration<UserTransaction> utReg;
88 private ServiceRegistration<TransactionSynchronizationRegistry> tsrReg;
89 private ServiceRegistration<? extends Repository> repositoryReg;
90 private ServiceRegistration<RepositoryFactory> repositoryFactoryReg;
91 private ServiceRegistration<UserAdmin> userAdminReg;
92
93 /*
94 * SERVICES IMPLEMENTATIONS
95 */
96 private NodeLogger logger;
97 private BitronixTransactionManager transactionManager;
98 private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry;
99 private OsgiJackrabbitRepositoryFactory repositoryFactory;
100 NodeRepository repository;
101 private NodeUserAdmin userAdmin;
102
103 // Members
104 private final static Log log = LogFactory.getLog(Kernel.class);
105 ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName());
106 private final BundleContext bc = Activator.getBundleContext();
107 private final NodeSecurity nodeSecurity;
108 private DataHttp dataHttp;
109 private NodeHttp nodeHttp;
110 private KernelThread kernelThread;
111
112 private Locale defaultLocale = null;
113 private List<Locale> locales = null;
114
115 public Kernel() {
116 nodeSecurity = new NodeSecurity();
117 }
118
119 final void init() {
120 Subject.doAs(nodeSecurity.getKernelSubject(),
121 new PrivilegedAction<Void>() {
122 @Override
123 public Void run() {
124 doInit();
125 return null;
126 }
127 });
128 }
129
130 private void doInit() {
131 long begin = System.currentTimeMillis();
132 ConfigurationAdmin conf = findConfigurationAdmin();
133 // Use CMS bundle classloader
134 ClassLoader currentContextCl = Thread.currentThread()
135 .getContextClassLoader();
136 Thread.currentThread().setContextClassLoader(
137 Kernel.class.getClassLoader());
138 try {
139 if (nodeSecurity.isFirstInit())
140 firstInit();
141
142 defaultLocale = new Locale(getFrameworkProp(I18N_DEFAULT_LOCALE,
143 ENGLISH.getLanguage()));
144 locales = asLocaleList(getFrameworkProp(I18N_LOCALES));
145
146 ServiceTracker<LogReaderService, LogReaderService> logReaderService = new ServiceTracker<LogReaderService, LogReaderService>(
147 bc, LogReaderService.class, null);
148 logReaderService.open();
149 logger = new NodeLogger(logReaderService.getService());
150 logReaderService.close();
151
152 // KernelUtils.logFrameworkProperties(log);
153
154 // Initialise services
155 initTransactionManager();
156 if (repository == null)
157 repository = new NodeRepository();
158 if (repositoryFactory == null)
159 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
160 userAdmin = new NodeUserAdmin(transactionManager, repository);
161
162 // HTTP
163 initWebServer(conf);
164 ServiceReference<ExtendedHttpService> sr = bc
165 .getServiceReference(ExtendedHttpService.class);
166 if (sr != null)
167 addHttpService(sr);
168
169 UserUi userUi = new UserUi();
170 Hashtable<String, String> props = new Hashtable<String, String>();
171 props.put("contextName", "user");
172 bc.registerService(ApplicationConfiguration.class, userUi, props);
173
174 // Kernel thread
175 kernelThread = new KernelThread(this);
176 kernelThread.setContextClassLoader(Kernel.class.getClassLoader());
177 kernelThread.start();
178
179 // Publish services to OSGi
180 publish();
181 } catch (Exception e) {
182 log.error("Cannot initialize Argeo CMS", e);
183 throw new ArgeoException("Cannot initialize", e);
184 } finally {
185 Thread.currentThread().setContextClassLoader(currentContextCl);
186 }
187
188 long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
189 log.info("## ARGEO CMS UP in " + (jvmUptime / 1000) + "."
190 + (jvmUptime % 1000) + "s ##");
191 long initDuration = System.currentTimeMillis() - begin;
192 if (log.isTraceEnabled())
193 log.trace("Kernel initialization took " + initDuration + "ms");
194 directorsCut(initDuration);
195 }
196
197 private void firstInit() {
198 log.info("## FIRST INIT ##");
199 String nodeInit = getFrameworkProp(NODE_INIT);
200 if (nodeInit == null)
201 nodeInit = "../../init";
202 if (nodeInit.startsWith("http")) {
203 remoteFirstInit(nodeInit);
204 return;
205 }
206 File initDir;
207 if (nodeInit.startsWith("."))
208 initDir = KernelUtils.getExecutionDir(nodeInit);
209 else
210 initDir = new File(nodeInit);
211 // TODO also uncompress archives
212 if (initDir.exists())
213 try {
214 FileUtils.copyDirectory(initDir, getOsgiInstanceDir(),
215 new FileFilter() {
216
217 @Override
218 public boolean accept(File pathname) {
219 if (pathname.getName().equals(".svn")
220 || pathname.getName().equals(".git"))
221 return false;
222 return true;
223 }
224 });
225 log.info("CMS initialized from " + initDir.getCanonicalPath());
226 } catch (IOException e) {
227 throw new CmsException("Cannot initialize from " + initDir, e);
228 }
229 }
230
231 private void remoteFirstInit(String uri) {
232 try {
233 repository = new NodeRepository();
234 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
235 Repository remoteRepository = ArgeoJcrUtils.getRepositoryByUri(
236 repositoryFactory, uri);
237 Session remoteSession = remoteRepository
238 .login(new SimpleCredentials("root", "demo".toCharArray()),
239 "main");
240 Session localSession = this.repository.login();
241 // FIXME register node type
242 // if (false)
243 // CndImporter.registerNodeTypes(null, localSession);
244 ByteArrayOutputStream out = new ByteArrayOutputStream();
245 remoteSession.exportSystemView("/", out, true, false);
246 ByteArrayInputStream in = new ByteArrayInputStream(
247 out.toByteArray());
248 localSession.importXML("/", in,
249 ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
250 // JcrUtils.copy(remoteSession.getRootNode(),
251 // localSession.getRootNode());
252 } catch (Exception e) {
253 throw new CmsException("Cannot first init from " + uri, e);
254 }
255 }
256
257 /** Can be null */
258 private ConfigurationAdmin findConfigurationAdmin() {
259 configurationAdmin = bc.getServiceReference(ConfigurationAdmin.class);
260 if (configurationAdmin == null) {
261 return null;
262 }
263 return bc.getService(configurationAdmin);
264 }
265
266 private void initTransactionManager() {
267 bitronix.tm.Configuration tmConf = TransactionManagerServices
268 .getConfiguration();
269 tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID));
270
271 // File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
272 // getOsgiInstancePath(DIR_TRANSACTIONS)));
273 File tmBaseDir = bc.getDataFile(DIR_TRANSACTIONS);
274 File tmDir1 = new File(tmBaseDir, "btm1");
275 tmDir1.mkdirs();
276 tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog")
277 .getAbsolutePath());
278 File tmDir2 = new File(tmBaseDir, "btm2");
279 tmDir2.mkdirs();
280 tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog")
281 .getAbsolutePath());
282 transactionManager = getTransactionManager();
283 transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
284 }
285
286 private void initWebServer(ConfigurationAdmin conf) {
287 String httpPort = getFrameworkProp("org.osgi.service.http.port");
288 String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
289 try {
290 if (httpPort != null || httpsPort != null) {
291 Hashtable<String, Object> jettyProps = new Hashtable<String, Object>();
292 if (httpPort != null) {
293 jettyProps.put(JettyConstants.HTTP_PORT, httpPort);
294 jettyProps.put(JettyConstants.HTTP_ENABLED, true);
295 }
296 if (httpsPort != null) {
297 jettyProps.put(JettyConstants.HTTPS_PORT, httpsPort);
298 jettyProps.put(JettyConstants.HTTPS_ENABLED, true);
299 jettyProps.put(JettyConstants.SSL_KEYSTORETYPE, "PKCS12");
300 jettyProps.put(JettyConstants.SSL_KEYSTORE, nodeSecurity
301 .getHttpServerKeyStore().getCanonicalPath());
302 jettyProps.put(JettyConstants.SSL_PASSWORD, "changeit");
303 jettyProps.put(JettyConstants.SSL_WANTCLIENTAUTH, true);
304 }
305 if (conf != null) {
306 // TODO make filter more generic
307 String filter = "(" + JettyConstants.HTTP_PORT + "="
308 + httpPort + ")";
309 if (conf.listConfigurations(filter) != null)
310 return;
311 Configuration jettyConf = conf.createFactoryConfiguration(
312 JETTY_FACTORY_PID, null);
313 jettyConf.update(jettyProps);
314 } else {
315 JettyConfigurator.startServer("default", jettyProps);
316 }
317 }
318 } catch (Exception e) {
319 throw new CmsException("Cannot initialize web server on "
320 + httpPortsMsg(httpPort, httpsPort), e);
321 }
322 }
323
324 @SuppressWarnings("unchecked")
325 private void publish() {
326 // Listen to service publication (also ours)
327 bc.addServiceListener(Kernel.this);
328
329 // Logging
330 loggerReg = bc.registerService(ArgeoLogger.class, logger, null);
331 // Transaction
332 tmReg = bc.registerService(TransactionManager.class,
333 transactionManager, null);
334 utReg = bc.registerService(UserTransaction.class, transactionManager,
335 null);
336 tsrReg = bc.registerService(TransactionSynchronizationRegistry.class,
337 transactionSynchronizationRegistry, null);
338 // User admin
339 userAdminReg = bc.registerService(UserAdmin.class, userAdmin,
340 userAdmin.currentState());
341 // JCR
342 Hashtable<String, String> regProps = new Hashtable<String, String>();
343 regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE);
344 repositoryReg = (ServiceRegistration<? extends Repository>) bc
345 .registerService(new String[] { Repository.class.getName(),
346 JackrabbitRepository.class.getName() }, repository,
347 regProps);
348 repositoryFactoryReg = bc.registerService(RepositoryFactory.class,
349 repositoryFactory, null);
350 }
351
352 void destroy() {
353 long begin = System.currentTimeMillis();
354 unpublish();
355
356 kernelThread.destroyAndJoin();
357
358 if (dataHttp != null)
359 dataHttp.destroy();
360 if (nodeHttp != null)
361 nodeHttp.destroy();
362 if (userAdmin != null)
363 userAdmin.destroy();
364 if (repository != null)
365 repository.destroy();
366 if (transactionManager != null)
367 transactionManager.shutdown();
368
369 bc.removeServiceListener(this);
370
371 // Clean hanging threads from Jackrabbit
372 TransientFileFactory.shutdown();
373
374 // Clean hanging Gogo shell thread
375 new GogoShellKiller().start();
376
377 nodeSecurity.destroy();
378 long duration = System.currentTimeMillis() - begin;
379 log.info("## ARGEO CMS DOWN in " + (duration / 1000) + "."
380 + (duration % 1000) + "s ##");
381 }
382
383 private void unpublish() {
384 userAdminReg.unregister();
385 repositoryFactoryReg.unregister();
386 repositoryReg.unregister();
387 tmReg.unregister();
388 utReg.unregister();
389 tsrReg.unregister();
390 loggerReg.unregister();
391 }
392
393 @Override
394 public void serviceChanged(ServiceEvent event) {
395 ServiceReference<?> sr = event.getServiceReference();
396 Object service = bc.getService(sr);
397 if (service instanceof Repository) {
398 Object jcrRepoAlias = sr
399 .getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
400 if (jcrRepoAlias != null) {// JCR repository
401 String alias = jcrRepoAlias.toString();
402 Repository repository = (Repository) bc.getService(sr);
403 Map<String, Object> props = new HashMap<String, Object>();
404 for (String key : sr.getPropertyKeys())
405 props.put(key, sr.getProperty(key));
406 if (ServiceEvent.REGISTERED == event.getType()) {
407 try {
408 repositoryFactory.register(repository, props);
409 dataHttp.registerRepositoryServlets(alias, repository);
410 } catch (Exception e) {
411 throw new CmsException(
412 "Could not publish JCR repository " + alias, e);
413 }
414 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
415 repositoryFactory.unregister(repository, props);
416 dataHttp.unregisterRepositoryServlets(alias);
417 }
418 }
419 } else if (service instanceof ExtendedHttpService) {
420 if (ServiceEvent.REGISTERED == event.getType()) {
421 addHttpService(sr);
422 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
423 dataHttp.destroy();
424 dataHttp = null;
425 }
426 }
427 }
428
429 private void addHttpService(ServiceReference<?> sr) {
430 // for (String key : sr.getPropertyKeys())
431 // log.debug(key + "=" + sr.getProperty(key));
432 ExtendedHttpService httpService = (ExtendedHttpService) bc
433 .getService(sr);
434 // TODO find constants
435 Object httpPort = sr.getProperty("http.port");
436 Object httpsPort = sr.getProperty("https.port");
437 dataHttp = new DataHttp(httpService, repository);
438 nodeHttp = new NodeHttp(httpService);
439 if (log.isDebugEnabled())
440 log.debug(httpPortsMsg(httpPort, httpsPort));
441 }
442
443 private String httpPortsMsg(Object httpPort, Object httpsPort) {
444 return "HTTP " + httpPort
445 + (httpsPort != null ? " - HTTPS " + httpsPort : "");
446 }
447
448 @Override
449 public Locale getDefaultLocale() {
450 return defaultLocale;
451 }
452
453 /** Can be null. */
454 @Override
455 public List<Locale> getLocales() {
456 return locales;
457 }
458
459 final private static void directorsCut(long initDuration) {
460 // final long ms = 128l + (long) (Math.random() * 128d);
461 long ms = initDuration / 100;
462 log.info("Spend " + ms + "ms"
463 + " reflecting on the progress brought to mankind"
464 + " by Free Software...");
465 long beginNano = System.nanoTime();
466 try {
467 Thread.sleep(ms, 0);
468 } catch (InterruptedException e) {
469 // silent
470 }
471 long durationNano = System.nanoTime() - beginNano;
472 final double M = 1000d * 1000d;
473 double sleepAccuracy = ((double) durationNano) / (ms * M);
474 if (log.isDebugEnabled())
475 log.debug("Sleep accuracy: "
476 + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100))
477 + " %");
478 }
479
480 /** Workaround for blocking Gogo shell by system shutdown. */
481 private class GogoShellKiller extends Thread {
482
483 public GogoShellKiller() {
484 super("Gogo shell killer");
485 setDaemon(true);
486 }
487
488 @Override
489 public void run() {
490 ThreadGroup rootTg = getRootThreadGroup(null);
491 Thread gogoShellThread = findGogoShellThread(rootTg);
492 if (gogoShellThread == null)
493 return;
494 while (getNonDaemonCount(rootTg) > 2) {
495 try {
496 Thread.sleep(100);
497 } catch (InterruptedException e) {
498 // silent
499 }
500 }
501 gogoShellThread = findGogoShellThread(rootTg);
502 if (gogoShellThread == null)
503 return;
504 System.exit(0);
505 }
506 }
507
508 private static ThreadGroup getRootThreadGroup(ThreadGroup tg) {
509 if (tg == null)
510 tg = Thread.currentThread().getThreadGroup();
511 if (tg.getParent() == null)
512 return tg;
513 else
514 return getRootThreadGroup(tg.getParent());
515 }
516
517 private static int getNonDaemonCount(ThreadGroup rootThreadGroup) {
518 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
519 rootThreadGroup.enumerate(threads);
520 int nonDameonCount = 0;
521 for (Thread t : threads)
522 if (!t.isDaemon())
523 nonDameonCount++;
524 return nonDameonCount;
525 }
526
527 private static Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
528 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
529 rootThreadGroup.enumerate(threads, true);
530 for (Thread thread : threads) {
531 if (thread.getName().equals("Gogo shell"))
532 return thread;
533 }
534 return null;
535 }
536
537 }