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