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
;
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
;
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
;
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
.osgi
.framework
.BundleContext
;
44 import org
.osgi
.framework
.ServiceEvent
;
45 import org
.osgi
.framework
.ServiceListener
;
46 import org
.osgi
.framework
.ServiceReference
;
47 import org
.osgi
.framework
.ServiceRegistration
;
48 import org
.osgi
.service
.cm
.Configuration
;
49 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
50 import org
.osgi
.service
.useradmin
.UserAdmin
;
52 import bitronix
.tm
.BitronixTransactionManager
;
53 import bitronix
.tm
.BitronixTransactionSynchronizationRegistry
;
54 import bitronix
.tm
.TransactionManagerServices
;
57 * Argeo CMS Kernel. Responsible for :
60 * <li>provisioning</li>
61 * <li>transaction</li>
63 * <li>local and remote file systems access</li>
67 final class Kernel
implements KernelHeader
, KernelConstants
, ServiceListener
{
71 private ServiceReference
<ConfigurationAdmin
> configurationAdmin
;
75 private ServiceRegistration
<ArgeoLogger
> loggerReg
;
76 private ServiceRegistration
<TransactionManager
> tmReg
;
77 private ServiceRegistration
<UserTransaction
> utReg
;
78 private ServiceRegistration
<TransactionSynchronizationRegistry
> tsrReg
;
79 private ServiceRegistration
<Repository
> repositoryReg
;
80 private ServiceRegistration
<RepositoryFactory
> repositoryFactoryReg
;
81 private ServiceRegistration
<UserAdmin
> userAdminReg
;
84 * SERVICES IMPLEMENTATIONS
86 private NodeLogger logger
;
87 private BitronixTransactionManager transactionManager
;
88 private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry
;
89 private OsgiJackrabbitRepositoryFactory repositoryFactory
;
90 NodeRepository repository
;
91 private NodeUserAdmin userAdmin
;
94 private final static Log log
= LogFactory
.getLog(Kernel
.class);
95 ThreadGroup threadGroup
= new ThreadGroup(Kernel
.class.getSimpleName());
96 private final BundleContext bc
= Activator
.getBundleContext();
97 private final NodeSecurity nodeSecurity
;
98 private DataHttp dataHttp
;
99 private KernelThread kernelThread
;
101 private Locale defaultLocale
= null;
102 private List
<Locale
> locales
= null;
105 nodeSecurity
= new NodeSecurity();
106 // log.debug(bc.getDataFile(""));
107 // log.debug(bc.getDataFile("test"));
111 Subject
.doAs(nodeSecurity
.getKernelSubject(),
112 new PrivilegedAction
<Void
>() {
121 private void doInit() {
122 long begin
= System
.currentTimeMillis();
123 ConfigurationAdmin conf
= findConfigurationAdmin();
124 // Use CMS bundle classloader
125 ClassLoader currentContextCl
= Thread
.currentThread()
126 .getContextClassLoader();
127 Thread
.currentThread().setContextClassLoader(
128 Kernel
.class.getClassLoader());
130 if (nodeSecurity
.isFirstInit())
133 defaultLocale
= new Locale(getFrameworkProp(I18N_DEFAULT_LOCALE
,
134 ENGLISH
.getLanguage()));
135 locales
= asLocaleList(getFrameworkProp(I18N_LOCALES
));
136 logger
= new NodeLogger();
138 // Initialise services
139 initTransactionManager();
140 repository
= new NodeRepository();
141 repositoryFactory
= new OsgiJackrabbitRepositoryFactory();
142 userAdmin
= new NodeUserAdmin(transactionManager
, repository
);
146 ServiceReference
<ExtendedHttpService
> sr
= bc
147 .getServiceReference(ExtendedHttpService
.class);
152 kernelThread
= new KernelThread(this);
153 kernelThread
.setContextClassLoader(Kernel
.class.getClassLoader());
154 kernelThread
.start();
156 // Publish services to OSGi
158 } catch (Exception e
) {
159 log
.error("Cannot initialize Argeo CMS", e
);
160 throw new ArgeoException("Cannot initialize", e
);
162 Thread
.currentThread().setContextClassLoader(currentContextCl
);
165 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
166 log
.info("## ARGEO CMS UP in " + (jvmUptime
/ 1000) + "."
167 + (jvmUptime
% 1000) + "s ##");
168 long initDuration
= System
.currentTimeMillis() - begin
;
169 if (log
.isTraceEnabled())
170 log
.trace("Kernel initialization took " + initDuration
+ "ms");
171 directorsCut(initDuration
);
174 private void firstInit() {
175 log
.info("## FIRST INIT ##");
176 File initDir
= new File(getFrameworkProp(NODE_INIT
,
177 KernelUtils
.getOsgiInstancePath("../../../init")));
178 // TODO also uncompress archives
179 if (initDir
.exists())
181 FileUtils
.copyDirectory(initDir
, getOsgiInstanceDir(),
185 public boolean accept(File pathname
) {
186 if (pathname
.getName().equals(".svn")
187 || pathname
.getName().equals(".git"))
192 log
.info("CMS initialized from " + initDir
.getCanonicalPath());
193 } catch (IOException e
) {
194 throw new CmsException("Cannot initialize from " + initDir
, e
);
199 private ConfigurationAdmin
findConfigurationAdmin() {
200 configurationAdmin
= bc
.getServiceReference(ConfigurationAdmin
.class);
201 if (configurationAdmin
== null) {
204 return bc
.getService(configurationAdmin
);
207 private void initTransactionManager() {
208 bitronix
.tm
.Configuration tmConf
= TransactionManagerServices
210 tmConf
.setServerId(getFrameworkProp(FRAMEWORK_UUID
));
212 // File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
213 // getOsgiInstancePath(DIR_TRANSACTIONS)));
214 File tmBaseDir
= bc
.getDataFile(DIR_TRANSACTIONS
);
215 File tmDir1
= new File(tmBaseDir
, "btm1");
217 tmConf
.setLogPart1Filename(new File(tmDir1
, tmDir1
.getName() + ".tlog")
219 File tmDir2
= new File(tmBaseDir
, "btm2");
221 tmConf
.setLogPart2Filename(new File(tmDir2
, tmDir2
.getName() + ".tlog")
223 transactionManager
= getTransactionManager();
224 transactionSynchronizationRegistry
= getTransactionSynchronizationRegistry();
227 private void initWebServer(ConfigurationAdmin conf
) {
228 String httpPort
= getFrameworkProp("org.osgi.service.http.port");
229 String httpsPort
= getFrameworkProp("org.osgi.service.http.port.secure");
231 if (httpPort
!= null || httpsPort
!= null) {
232 Hashtable
<String
, Object
> jettyProps
= new Hashtable
<String
, Object
>();
233 if (httpPort
!= null) {
234 jettyProps
.put(JettyConstants
.HTTP_PORT
, httpPort
);
235 jettyProps
.put(JettyConstants
.HTTP_ENABLED
, true);
237 if (httpsPort
!= null) {
238 jettyProps
.put(JettyConstants
.HTTPS_PORT
, httpsPort
);
239 jettyProps
.put(JettyConstants
.HTTPS_ENABLED
, true);
240 jettyProps
.put(JettyConstants
.SSL_KEYSTORETYPE
, "PKCS12");
241 jettyProps
.put(JettyConstants
.SSL_KEYSTORE
, nodeSecurity
242 .getHttpServerKeyStore().getCanonicalPath());
243 jettyProps
.put(JettyConstants
.SSL_PASSWORD
, "changeit");
244 jettyProps
.put(JettyConstants
.SSL_WANTCLIENTAUTH
, true);
247 // TODO make filter more generic
248 String filter
= "(" + JettyConstants
.HTTP_PORT
+ "="
250 if (conf
.listConfigurations(filter
) != null)
252 Configuration jettyConf
= conf
.createFactoryConfiguration(
253 JETTY_FACTORY_PID
, null);
254 jettyConf
.update(jettyProps
);
256 JettyConfigurator
.startServer("default", jettyProps
);
259 } catch (Exception e
) {
260 throw new CmsException("Cannot initialize web server on "
261 + httpPortsMsg(httpPort
, httpsPort
), e
);
265 private void publish() {
266 // Listen to service publication (also ours)
267 bc
.addServiceListener(Kernel
.this);
270 loggerReg
= bc
.registerService(ArgeoLogger
.class, logger
, null);
272 tmReg
= bc
.registerService(TransactionManager
.class,
273 transactionManager
, null);
274 utReg
= bc
.registerService(UserTransaction
.class, transactionManager
,
276 tsrReg
= bc
.registerService(TransactionSynchronizationRegistry
.class,
277 transactionSynchronizationRegistry
, null);
279 userAdminReg
= bc
.registerService(UserAdmin
.class, userAdmin
,
280 userAdmin
.currentState());
282 Hashtable
<String
, String
> regProps
= new Hashtable
<String
, String
>();
283 regProps
.put(JCR_REPOSITORY_ALIAS
, ALIAS_NODE
);
284 repositoryReg
= bc
.registerService(Repository
.class, repository
,
286 repositoryFactoryReg
= bc
.registerService(RepositoryFactory
.class,
287 repositoryFactory
, null);
291 long begin
= System
.currentTimeMillis();
294 kernelThread
.destroyAndJoin();
296 if (dataHttp
!= null)
298 if (userAdmin
!= null)
300 if (repository
!= null)
301 repository
.destroy();
302 if (transactionManager
!= null)
303 transactionManager
.shutdown();
305 bc
.removeServiceListener(this);
307 // Clean hanging threads from Jackrabbit
308 TransientFileFactory
.shutdown();
310 // Clean hanging Gogo shell thread
311 new GogoShellKiller().start();
313 nodeSecurity
.destroy();
314 long duration
= System
.currentTimeMillis() - begin
;
315 log
.info("## ARGEO CMS DOWN in " + (duration
/ 1000) + "."
316 + (duration
% 1000) + "s ##");
319 private void unpublish() {
320 userAdminReg
.unregister();
321 repositoryFactoryReg
.unregister();
322 repositoryReg
.unregister();
326 loggerReg
.unregister();
330 public void serviceChanged(ServiceEvent event
) {
331 ServiceReference
<?
> sr
= event
.getServiceReference();
332 Object service
= bc
.getService(sr
);
333 if (service
instanceof Repository
) {
334 Object jcrRepoAlias
= sr
335 .getProperty(ArgeoJcrConstants
.JCR_REPOSITORY_ALIAS
);
336 if (jcrRepoAlias
!= null) {// JCR repository
337 String alias
= jcrRepoAlias
.toString();
338 Repository repository
= (Repository
) bc
.getService(sr
);
339 Map
<String
, Object
> props
= new HashMap
<String
, Object
>();
340 for (String key
: sr
.getPropertyKeys())
341 props
.put(key
, sr
.getProperty(key
));
342 if (ServiceEvent
.REGISTERED
== event
.getType()) {
344 repositoryFactory
.register(repository
, props
);
345 dataHttp
.registerRepositoryServlets(alias
, repository
);
346 } catch (Exception e
) {
347 throw new CmsException(
348 "Could not publish JCR repository " + alias
, e
);
350 } else if (ServiceEvent
.UNREGISTERING
== event
.getType()) {
351 repositoryFactory
.unregister(repository
, props
);
352 dataHttp
.unregisterRepositoryServlets(alias
);
355 } else if (service
instanceof ExtendedHttpService
) {
356 if (ServiceEvent
.REGISTERED
== event
.getType()) {
358 } else if (ServiceEvent
.UNREGISTERING
== event
.getType()) {
365 private void addHttpService(ServiceReference
<?
> sr
) {
366 // for (String key : sr.getPropertyKeys())
367 // log.debug(key + "=" + sr.getProperty(key));
368 ExtendedHttpService httpService
= (ExtendedHttpService
) bc
370 // TODO find constants
371 Object httpPort
= sr
.getProperty("http.port");
372 Object httpsPort
= sr
.getProperty("https.port");
373 dataHttp
= new DataHttp(httpService
, repository
);
374 if (log
.isDebugEnabled())
375 log
.debug(httpPortsMsg(httpPort
, httpsPort
));
378 private String
httpPortsMsg(Object httpPort
, Object httpsPort
) {
379 return "HTTP " + httpPort
380 + (httpsPort
!= null ?
" - HTTPS " + httpsPort
: "");
384 public Locale
getDefaultLocale() {
385 return defaultLocale
;
390 public List
<Locale
> getLocales() {
394 final private static void directorsCut(long initDuration
) {
395 // final long ms = 128l + (long) (Math.random() * 128d);
396 long ms
= initDuration
/ 100;
397 log
.info("Spend " + ms
+ "ms"
398 + " reflecting on the progress brought to mankind"
399 + " by Free Software...");
400 long beginNano
= System
.nanoTime();
403 } catch (InterruptedException e
) {
406 long durationNano
= System
.nanoTime() - beginNano
;
407 final double M
= 1000d
* 1000d
;
408 double sleepAccuracy
= ((double) durationNano
) / (ms
* M
);
409 if (log
.isDebugEnabled())
410 log
.debug("Sleep accuracy: "
411 + String
.format("%.2f", 100 - (sleepAccuracy
* 100 - 100))
415 /** Workaround for blocking Gogo shell by system shutdown. */
416 private class GogoShellKiller
extends Thread
{
418 public GogoShellKiller() {
419 super("Gogo shell killer");
425 ThreadGroup rootTg
= getRootThreadGroup(null);
426 Thread gogoShellThread
= findGogoShellThread(rootTg
);
427 if (gogoShellThread
== null)
429 while (getNonDaemonCount(rootTg
) > 2) {
432 } catch (InterruptedException e
) {
436 gogoShellThread
= findGogoShellThread(rootTg
);
437 if (gogoShellThread
== null)
443 private static ThreadGroup
getRootThreadGroup(ThreadGroup tg
) {
445 tg
= Thread
.currentThread().getThreadGroup();
446 if (tg
.getParent() == null)
449 return getRootThreadGroup(tg
.getParent());
452 private static int getNonDaemonCount(ThreadGroup rootThreadGroup
) {
453 Thread
[] threads
= new Thread
[rootThreadGroup
.activeCount()];
454 rootThreadGroup
.enumerate(threads
);
455 int nonDameonCount
= 0;
456 for (Thread t
: threads
)
459 return nonDameonCount
;
462 private static Thread
findGogoShellThread(ThreadGroup rootThreadGroup
) {
463 Thread
[] threads
= new Thread
[rootThreadGroup
.activeCount()];
464 rootThreadGroup
.enumerate(threads
, true);
465 for (Thread thread
: threads
) {
466 if (thread
.getName().equals("Gogo shell"))