1 package org
.argeo
.cms
.internal
.kernel
;
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
;
13 import java
.io
.ByteArrayInputStream
;
14 import java
.io
.ByteArrayOutputStream
;
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
;
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
.security
.auth
.login
.LoginContext
;
33 import javax
.security
.auth
.login
.LoginException
;
34 import javax
.transaction
.TransactionManager
;
35 import javax
.transaction
.TransactionSynchronizationRegistry
;
36 import javax
.transaction
.UserTransaction
;
38 import org
.apache
.commons
.io
.FileUtils
;
39 import org
.apache
.commons
.logging
.Log
;
40 import org
.apache
.commons
.logging
.LogFactory
;
41 import org
.apache
.jackrabbit
.api
.JackrabbitRepository
;
42 import org
.apache
.jackrabbit
.util
.TransientFileFactory
;
43 import org
.argeo
.ArgeoException
;
44 import org
.argeo
.ArgeoLogger
;
45 import org
.argeo
.cms
.CmsException
;
46 import org
.argeo
.cms
.maintenance
.MaintenanceUi
;
47 import org
.argeo
.jackrabbit
.OsgiJackrabbitRepositoryFactory
;
48 import org
.argeo
.jcr
.ArgeoJcrConstants
;
49 import org
.argeo
.jcr
.ArgeoJcrUtils
;
50 import org
.eclipse
.equinox
.http
.jetty
.JettyConfigurator
;
51 import org
.eclipse
.equinox
.http
.jetty
.JettyConstants
;
52 import org
.eclipse
.equinox
.http
.servlet
.ExtendedHttpService
;
53 import org
.eclipse
.rap
.rwt
.application
.ApplicationConfiguration
;
54 import org
.osgi
.framework
.BundleContext
;
55 import org
.osgi
.framework
.ServiceEvent
;
56 import org
.osgi
.framework
.ServiceListener
;
57 import org
.osgi
.framework
.ServiceReference
;
58 import org
.osgi
.framework
.ServiceRegistration
;
59 import org
.osgi
.framework
.startlevel
.BundleStartLevel
;
60 import org
.osgi
.service
.cm
.Configuration
;
61 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
62 import org
.osgi
.service
.log
.LogReaderService
;
63 import org
.osgi
.service
.useradmin
.UserAdmin
;
64 import org
.osgi
.util
.tracker
.ServiceTracker
;
66 import bitronix
.tm
.BitronixTransactionManager
;
67 import bitronix
.tm
.BitronixTransactionSynchronizationRegistry
;
68 import bitronix
.tm
.TransactionManagerServices
;
71 * Argeo CMS Kernel. Responsible for :
74 * <li>provisioning</li>
75 * <li>transaction</li>
77 * <li>local and remote file systems access</li>
81 final class Kernel
implements KernelHeader
, KernelConstants
, ServiceListener
{
85 private ServiceReference
<ConfigurationAdmin
> configurationAdmin
;
89 private ServiceRegistration
<ArgeoLogger
> loggerReg
;
90 private ServiceRegistration
<TransactionManager
> tmReg
;
91 private ServiceRegistration
<UserTransaction
> utReg
;
92 private ServiceRegistration
<TransactionSynchronizationRegistry
> tsrReg
;
93 private ServiceRegistration
<?
extends Repository
> repositoryReg
;
94 private ServiceRegistration
<RepositoryFactory
> repositoryFactoryReg
;
95 private ServiceRegistration
<UserAdmin
> userAdminReg
;
98 * SERVICES IMPLEMENTATIONS
100 private NodeLogger logger
;
101 private BitronixTransactionManager transactionManager
;
102 private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry
;
103 private OsgiJackrabbitRepositoryFactory repositoryFactory
;
104 NodeRepository repository
;
105 private NodeUserAdmin userAdmin
;
108 private final static Log log
= LogFactory
.getLog(Kernel
.class);
109 ThreadGroup threadGroup
= new ThreadGroup(Kernel
.class.getSimpleName());
110 private final BundleContext bc
= Activator
.getBundleContext();
111 private final NodeSecurity nodeSecurity
;
112 private DataHttp dataHttp
;
113 private NodeHttp nodeHttp
;
114 private KernelThread kernelThread
;
116 private Locale defaultLocale
= null;
117 private List
<Locale
> locales
= null;
120 // KernelUtils.logFrameworkProperties(log);
121 nodeSecurity
= new NodeSecurity();
125 Subject
.doAs(nodeSecurity
.getKernelSubject(),
126 new PrivilegedAction
<Void
>() {
135 private void doInit() {
136 long begin
= System
.currentTimeMillis();
137 // Use CMS bundle classloader
138 ClassLoader currentContextCl
= Thread
.currentThread()
139 .getContextClassLoader();
140 Thread
.currentThread().setContextClassLoader(
141 Kernel
.class.getClassLoader());
143 if (nodeSecurity
.isFirstInit())
146 defaultLocale
= new Locale(getFrameworkProp(I18N_DEFAULT_LOCALE
,
147 ENGLISH
.getLanguage()));
148 locales
= asLocaleList(getFrameworkProp(I18N_LOCALES
));
150 ServiceTracker
<LogReaderService
, LogReaderService
> logReaderService
= new ServiceTracker
<LogReaderService
, LogReaderService
>(
151 bc
, LogReaderService
.class, null);
152 logReaderService
.open();
153 logger
= new NodeLogger(logReaderService
.getService());
154 logReaderService
.close();
160 } catch (Exception e
) {
161 log
.error("Cannot initialize Argeo CMS", e
);
162 throw new ArgeoException("Cannot initialize", e
);
164 Thread
.currentThread().setContextClassLoader(currentContextCl
);
165 // FIXME better manage lifecycle.
167 new LoginContext(LOGIN_CONTEXT_KERNEL
,
168 nodeSecurity
.getKernelSubject()).logout();
169 } catch (LoginException e
) {
174 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
175 log
.info("## ARGEO CMS UP in " + (jvmUptime
/ 1000) + "."
176 + (jvmUptime
% 1000) + "s ##");
177 long initDuration
= System
.currentTimeMillis() - begin
;
178 if (log
.isTraceEnabled())
179 log
.trace("Kernel initialization took " + initDuration
+ "ms");
180 directorsCut(initDuration
);
183 private void normalInit() {
184 ConfigurationAdmin conf
= findConfigurationAdmin();
185 // Initialise services
186 initTransactionManager();
187 if (repository
== null)
188 repository
= new NodeRepository();
189 if (repositoryFactory
== null)
190 repositoryFactory
= new OsgiJackrabbitRepositoryFactory();
191 userAdmin
= new NodeUserAdmin(transactionManager
, repository
);
195 ServiceReference
<ExtendedHttpService
> sr
= bc
196 .getServiceReference(ExtendedHttpService
.class);
201 UserUi userUi
= new UserUi();
202 Hashtable
<String
, String
> props
= new Hashtable
<String
, String
>();
203 props
.put("contextName", "user");
204 bc
.registerService(ApplicationConfiguration
.class, userUi
, props
);
207 kernelThread
= new KernelThread(this);
208 kernelThread
.setContextClassLoader(Kernel
.class.getClassLoader());
209 kernelThread
.start();
211 // Publish services to OSGi
215 private boolean isMaintenance() {
216 String startLevel
= KernelUtils
.getFrameworkProp("osgi.startLevel");
217 if (startLevel
== null)
219 int bundleStartLevel
= bc
.getBundle().adapt(BundleStartLevel
.class)
221 // int frameworkStartLevel =
222 // bc.getBundle(0).adapt(BundleStartLevel.class)
224 int frameworkStartLevel
= Integer
.parseInt(startLevel
);
225 // int frameworkStartLevel = bc.getBundle(0)
226 // .adapt(FrameworkStartLevel.class).getStartLevel();
227 return bundleStartLevel
== frameworkStartLevel
;
230 private void maintenanceInit() {
231 log
.info("## MAINTENANCE ##");
232 bc
.addServiceListener(Kernel
.this);
234 MaintenanceUi maintenanceUi
= new MaintenanceUi();
235 Hashtable
<String
, String
> props
= new Hashtable
<String
, String
>();
236 props
.put("contextName", "maintenance");
237 bc
.registerService(ApplicationConfiguration
.class, maintenanceUi
, props
);
240 private void firstInit() {
241 log
.info("## FIRST INIT ##");
242 String nodeInit
= getFrameworkProp(NODE_INIT
);
243 if (nodeInit
== null)
244 nodeInit
= "../../init";
245 if (nodeInit
.startsWith("http")) {
246 remoteFirstInit(nodeInit
);
250 if (nodeInit
.startsWith("."))
251 initDir
= KernelUtils
.getExecutionDir(nodeInit
);
253 initDir
= new File(nodeInit
);
254 // TODO also uncompress archives
255 if (initDir
.exists())
257 FileUtils
.copyDirectory(initDir
, getOsgiInstanceDir(),
261 public boolean accept(File pathname
) {
262 if (pathname
.getName().equals(".svn")
263 || pathname
.getName().equals(".git"))
268 log
.info("CMS initialized from " + initDir
.getCanonicalPath());
269 } catch (IOException e
) {
270 throw new CmsException("Cannot initialize from " + initDir
, e
);
274 private void remoteFirstInit(String uri
) {
276 repository
= new NodeRepository();
277 repositoryFactory
= new OsgiJackrabbitRepositoryFactory();
278 Repository remoteRepository
= ArgeoJcrUtils
.getRepositoryByUri(
279 repositoryFactory
, uri
);
280 Session remoteSession
= remoteRepository
281 .login(new SimpleCredentials("root", "demo".toCharArray()),
283 Session localSession
= this.repository
.login();
284 // FIXME register node type
286 // CndImporter.registerNodeTypes(null, localSession);
287 ByteArrayOutputStream out
= new ByteArrayOutputStream();
288 remoteSession
.exportSystemView("/", out
, true, false);
289 ByteArrayInputStream in
= new ByteArrayInputStream(
291 localSession
.importXML("/", in
,
292 ImportUUIDBehavior
.IMPORT_UUID_COLLISION_THROW
);
293 // JcrUtils.copy(remoteSession.getRootNode(),
294 // localSession.getRootNode());
295 } catch (Exception e
) {
296 throw new CmsException("Cannot first init from " + uri
, e
);
301 private ConfigurationAdmin
findConfigurationAdmin() {
302 configurationAdmin
= bc
.getServiceReference(ConfigurationAdmin
.class);
303 if (configurationAdmin
== null) {
306 return bc
.getService(configurationAdmin
);
309 private void initTransactionManager() {
310 bitronix
.tm
.Configuration tmConf
= TransactionManagerServices
312 tmConf
.setServerId(getFrameworkProp(FRAMEWORK_UUID
));
314 // File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
315 // getOsgiInstancePath(DIR_TRANSACTIONS)));
316 File tmBaseDir
= bc
.getDataFile(DIR_TRANSACTIONS
);
317 File tmDir1
= new File(tmBaseDir
, "btm1");
319 tmConf
.setLogPart1Filename(new File(tmDir1
, tmDir1
.getName() + ".tlog")
321 File tmDir2
= new File(tmBaseDir
, "btm2");
323 tmConf
.setLogPart2Filename(new File(tmDir2
, tmDir2
.getName() + ".tlog")
325 transactionManager
= getTransactionManager();
326 transactionSynchronizationRegistry
= getTransactionSynchronizationRegistry();
329 private void initWebServer(ConfigurationAdmin conf
) {
330 String httpPort
= getFrameworkProp("org.osgi.service.http.port");
331 String httpsPort
= getFrameworkProp("org.osgi.service.http.port.secure");
333 if (httpPort
!= null || httpsPort
!= null) {
334 Hashtable
<String
, Object
> jettyProps
= new Hashtable
<String
, Object
>();
335 if (httpPort
!= null) {
336 jettyProps
.put(JettyConstants
.HTTP_PORT
, httpPort
);
337 jettyProps
.put(JettyConstants
.HTTP_ENABLED
, true);
339 if (httpsPort
!= null) {
340 jettyProps
.put(JettyConstants
.HTTPS_PORT
, httpsPort
);
341 jettyProps
.put(JettyConstants
.HTTPS_ENABLED
, true);
342 jettyProps
.put(JettyConstants
.SSL_KEYSTORETYPE
, "PKCS12");
343 jettyProps
.put(JettyConstants
.SSL_KEYSTORE
, nodeSecurity
344 .getHttpServerKeyStore().getCanonicalPath());
345 jettyProps
.put(JettyConstants
.SSL_PASSWORD
, "changeit");
346 jettyProps
.put(JettyConstants
.SSL_WANTCLIENTAUTH
, true);
349 // TODO make filter more generic
350 String filter
= "(" + JettyConstants
.HTTP_PORT
+ "="
352 if (conf
.listConfigurations(filter
) != null)
354 Configuration jettyConf
= conf
.createFactoryConfiguration(
355 JETTY_FACTORY_PID
, null);
356 jettyConf
.update(jettyProps
);
358 JettyConfigurator
.startServer("default", jettyProps
);
361 } catch (Exception e
) {
362 throw new CmsException("Cannot initialize web server on "
363 + httpPortsMsg(httpPort
, httpsPort
), e
);
367 @SuppressWarnings("unchecked")
368 private void publish() {
369 // Listen to service publication (also ours)
370 bc
.addServiceListener(Kernel
.this);
373 loggerReg
= bc
.registerService(ArgeoLogger
.class, logger
, null);
375 tmReg
= bc
.registerService(TransactionManager
.class,
376 transactionManager
, null);
377 utReg
= bc
.registerService(UserTransaction
.class, transactionManager
,
379 tsrReg
= bc
.registerService(TransactionSynchronizationRegistry
.class,
380 transactionSynchronizationRegistry
, null);
382 userAdminReg
= bc
.registerService(UserAdmin
.class, userAdmin
,
383 userAdmin
.currentState());
385 Hashtable
<String
, String
> regProps
= new Hashtable
<String
, String
>();
386 regProps
.put(JCR_REPOSITORY_ALIAS
, ALIAS_NODE
);
387 repositoryReg
= (ServiceRegistration
<?
extends Repository
>) bc
388 .registerService(new String
[] { Repository
.class.getName(),
389 JackrabbitRepository
.class.getName() }, repository
,
391 repositoryFactoryReg
= bc
.registerService(RepositoryFactory
.class,
392 repositoryFactory
, null);
396 long begin
= System
.currentTimeMillis();
399 kernelThread
.destroyAndJoin();
401 if (dataHttp
!= null)
403 if (nodeHttp
!= null)
405 if (userAdmin
!= null)
407 if (repository
!= null)
408 repository
.destroy();
409 if (transactionManager
!= null)
410 transactionManager
.shutdown();
412 bc
.removeServiceListener(this);
414 // Clean hanging threads from Jackrabbit
415 TransientFileFactory
.shutdown();
417 // Clean hanging Gogo shell thread
418 new GogoShellKiller().start();
420 nodeSecurity
.destroy();
421 long duration
= System
.currentTimeMillis() - begin
;
422 log
.info("## ARGEO CMS DOWN in " + (duration
/ 1000) + "."
423 + (duration
% 1000) + "s ##");
426 private void unpublish() {
427 userAdminReg
.unregister();
428 repositoryFactoryReg
.unregister();
429 repositoryReg
.unregister();
433 loggerReg
.unregister();
437 public void serviceChanged(ServiceEvent event
) {
438 ServiceReference
<?
> sr
= event
.getServiceReference();
439 Object service
= bc
.getService(sr
);
440 if (service
instanceof Repository
) {
441 Object jcrRepoAlias
= sr
442 .getProperty(ArgeoJcrConstants
.JCR_REPOSITORY_ALIAS
);
443 if (jcrRepoAlias
!= null) {// JCR repository
444 String alias
= jcrRepoAlias
.toString();
445 Repository repository
= (Repository
) bc
.getService(sr
);
446 Map
<String
, Object
> props
= new HashMap
<String
, Object
>();
447 for (String key
: sr
.getPropertyKeys())
448 props
.put(key
, sr
.getProperty(key
));
449 if (ServiceEvent
.REGISTERED
== event
.getType()) {
451 repositoryFactory
.register(repository
, props
);
452 dataHttp
.registerRepositoryServlets(alias
, repository
);
453 } catch (Exception e
) {
454 throw new CmsException(
455 "Could not publish JCR repository " + alias
, e
);
457 } else if (ServiceEvent
.UNREGISTERING
== event
.getType()) {
458 repositoryFactory
.unregister(repository
, props
);
459 dataHttp
.unregisterRepositoryServlets(alias
);
462 } else if (service
instanceof ExtendedHttpService
) {
463 if (ServiceEvent
.REGISTERED
== event
.getType()) {
465 } else if (ServiceEvent
.UNREGISTERING
== event
.getType()) {
472 private void addHttpService(ServiceReference
<?
> sr
) {
473 // for (String key : sr.getPropertyKeys())
474 // log.debug(key + "=" + sr.getProperty(key));
475 ExtendedHttpService httpService
= (ExtendedHttpService
) bc
477 // TODO find constants
478 Object httpPort
= sr
.getProperty("http.port");
479 Object httpsPort
= sr
.getProperty("https.port");
480 dataHttp
= new DataHttp(httpService
);
481 nodeHttp
= new NodeHttp(httpService
, repository
);
482 if (log
.isDebugEnabled())
483 log
.debug(httpPortsMsg(httpPort
, httpsPort
));
486 private String
httpPortsMsg(Object httpPort
, Object httpsPort
) {
487 return "HTTP " + httpPort
488 + (httpsPort
!= null ?
" - HTTPS " + httpsPort
: "");
492 public Locale
getDefaultLocale() {
493 return defaultLocale
;
498 public List
<Locale
> getLocales() {
502 final private static void directorsCut(long initDuration
) {
503 // final long ms = 128l + (long) (Math.random() * 128d);
504 long ms
= initDuration
/ 100;
505 log
.info("Spend " + ms
+ "ms"
506 + " reflecting on the progress brought to mankind"
507 + " by Free Software...");
508 long beginNano
= System
.nanoTime();
511 } catch (InterruptedException e
) {
514 long durationNano
= System
.nanoTime() - beginNano
;
515 final double M
= 1000d
* 1000d
;
516 double sleepAccuracy
= ((double) durationNano
) / (ms
* M
);
517 if (log
.isDebugEnabled())
518 log
.debug("Sleep accuracy: "
519 + String
.format("%.2f", 100 - (sleepAccuracy
* 100 - 100))
523 /** Workaround for blocking Gogo shell by system shutdown. */
524 private class GogoShellKiller
extends Thread
{
526 public GogoShellKiller() {
527 super("Gogo shell killer");
533 ThreadGroup rootTg
= getRootThreadGroup(null);
534 Thread gogoShellThread
= findGogoShellThread(rootTg
);
535 if (gogoShellThread
== null)
537 while (getNonDaemonCount(rootTg
) > 2) {
540 } catch (InterruptedException e
) {
544 gogoShellThread
= findGogoShellThread(rootTg
);
545 if (gogoShellThread
== null)
551 private static ThreadGroup
getRootThreadGroup(ThreadGroup tg
) {
553 tg
= Thread
.currentThread().getThreadGroup();
554 if (tg
.getParent() == null)
557 return getRootThreadGroup(tg
.getParent());
560 private static int getNonDaemonCount(ThreadGroup rootThreadGroup
) {
561 Thread
[] threads
= new Thread
[rootThreadGroup
.activeCount()];
562 rootThreadGroup
.enumerate(threads
);
563 int nonDameonCount
= 0;
564 for (Thread t
: threads
)
565 if (t
!= null && !t
.isDaemon())
567 return nonDameonCount
;
570 private static Thread
findGogoShellThread(ThreadGroup rootThreadGroup
) {
571 Thread
[] threads
= new Thread
[rootThreadGroup
.activeCount()];
572 rootThreadGroup
.enumerate(threads
, true);
573 for (Thread thread
: threads
) {
574 if (thread
.getName().equals("Gogo shell"))