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