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
.auth
.LocaleChoice
.asLocaleList
;
7 import static org
.argeo
.cms
.internal
.kernel
.KernelUtils
.getFrameworkProp
;
10 import java
.io
.IOException
;
11 import java
.net
.InetAddress
;
13 import java
.net
.UnknownHostException
;
14 import java
.nio
.file
.DirectoryStream
;
15 import java
.nio
.file
.Files
;
16 import java
.nio
.file
.Path
;
17 import java
.util
.ArrayList
;
18 import java
.util
.Dictionary
;
19 import java
.util
.Hashtable
;
20 import java
.util
.List
;
21 import java
.util
.Locale
;
22 import java
.util
.UUID
;
24 import javax
.jcr
.RepositoryFactory
;
25 import javax
.transaction
.TransactionManager
;
26 import javax
.transaction
.TransactionSynchronizationRegistry
;
27 import javax
.transaction
.UserTransaction
;
29 import org
.apache
.commons
.logging
.Log
;
30 import org
.apache
.commons
.logging
.LogFactory
;
31 import org
.apache
.jackrabbit
.core
.RepositoryContext
;
32 import org
.argeo
.cms
.CmsException
;
33 import org
.argeo
.cms
.maintenance
.MaintenanceUi
;
34 import org
.argeo
.jcr
.ArgeoJcrConstants
;
35 import org
.argeo
.node
.NodeConstants
;
36 import org
.argeo
.node
.NodeDeployment
;
37 import org
.argeo
.node
.NodeState
;
38 import org
.argeo
.node
.RepoConf
;
39 import org
.argeo
.util
.LangUtils
;
40 import org
.eclipse
.equinox
.http
.jetty
.JettyConfigurator
;
41 import org
.eclipse
.equinox
.http
.jetty
.JettyConstants
;
42 import org
.eclipse
.rap
.rwt
.application
.ApplicationConfiguration
;
43 import org
.osgi
.framework
.Bundle
;
44 import org
.osgi
.framework
.BundleContext
;
45 import org
.osgi
.framework
.Constants
;
46 import org
.osgi
.framework
.FrameworkUtil
;
47 import org
.osgi
.framework
.ServiceReference
;
48 import org
.osgi
.service
.cm
.Configuration
;
49 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
50 import org
.osgi
.service
.cm
.ConfigurationException
;
51 import org
.osgi
.service
.cm
.ManagedService
;
52 import org
.osgi
.service
.cm
.ManagedServiceFactory
;
53 import org
.osgi
.service
.http
.HttpService
;
54 import org
.osgi
.service
.metatype
.MetaTypeProvider
;
55 import org
.osgi
.service
.useradmin
.UserAdmin
;
56 import org
.osgi
.util
.tracker
.ServiceTracker
;
57 import org
.osgi
.util
.tracker
.ServiceTrackerCustomizer
;
59 import bitronix
.tm
.BitronixTransactionManager
;
60 import bitronix
.tm
.BitronixTransactionSynchronizationRegistry
;
61 import bitronix
.tm
.TransactionManagerServices
;
63 public class CmsState
implements NodeState
, ManagedService
{
64 private final Log log
= LogFactory
.getLog(CmsState
.class);
65 private final BundleContext bc
= FrameworkUtil
.getBundle(CmsState
.class).getBundleContext();
67 // avoid dependency to RWT OSGi
68 private final static String PROPERTY_CONTEXT_NAME
= "contextName";
71 private ConfigurationAdmin configurationAdmin
;
74 private Locale defaultLocale
;
75 private List
<Locale
> locales
= null;
77 // private BitronixTransactionManager transactionManager;
78 // private BitronixTransactionSynchronizationRegistry
79 // transactionSynchronizationRegistry;
80 // private NodeRepositoryFactory repositoryFactory;
81 // private NodeUserAdmin userAdmin;
82 // private RepositoryServiceFactory repositoryServiceFactory;
83 // private RepositoryService repositoryService;
86 private final CmsDeployment nodeDeployment
= new CmsDeployment();
88 private boolean cleanState
= false;
89 private URI nodeRepoUri
= null;
91 private ThreadGroup threadGroup
= new ThreadGroup("CMS");
92 private KernelThread kernelThread
;
93 private List
<Runnable
> shutdownHooks
= new ArrayList
<>();
95 private String hostname
;
99 this.hostname
= InetAddress
.getLocalHost().getHostName();
100 } catch (UnknownHostException e
) {
101 log
.error("Cannot set hostname", e
);
106 public void updated(Dictionary
<String
, ?
> properties
) throws ConfigurationException
{
107 if (properties
== null) {
108 // TODO this should not happen anymore
109 this.cleanState
= true;
110 if (log
.isTraceEnabled())
111 log
.trace("Clean state");
114 String stateUuid
= properties
.get(NodeConstants
.CN
).toString();
115 String frameworkUuid
= KernelUtils
.getFrameworkProp(Constants
.FRAMEWORK_UUID
);
116 this.cleanState
= stateUuid
.equals(frameworkUuid
);
119 if (log
.isDebugEnabled())
120 log
.debug("## CMS STARTED " + stateUuid
+ (cleanState ?
" (clean state) " : " ")
121 + LangUtils
.toJson(properties
, true));
122 configurationAdmin
= bc
.getService(bc
.getServiceReference(ConfigurationAdmin
.class));
124 nodeRepoUri
= KernelUtils
.getOsgiInstanceUri("repos/node");
126 initI18n(properties
);
128 initDeployConfigs(properties
);
130 initNodeDeployment();
133 kernelThread
= new KernelThread(threadGroup
, "Kernel Thread");
134 kernelThread
.setContextClassLoader(getClass().getClassLoader());
135 kernelThread
.start();
136 } catch (Exception e
) {
137 throw new CmsException("Cannot get configuration", e
);
141 private void initI18n(Dictionary
<String
, ?
> stateProps
) {
142 Object defaultLocaleValue
= stateProps
.get(NodeConstants
.I18N_DEFAULT_LOCALE
);
143 defaultLocale
= defaultLocaleValue
!= null ?
new Locale(defaultLocaleValue
.toString())
144 : new Locale(ENGLISH
.getLanguage());
145 locales
= asLocaleList(stateProps
.get(NodeConstants
.I18N_LOCALES
));
148 private void initServices() {
150 new ServiceTracker
<HttpService
, HttpService
>(bc
, HttpService
.class, new PrepareHttpStc()).open();
151 new ServiceTracker
<>(bc
, RepositoryContext
.class, new RepositoryContextStc()).open();
153 initTransactionManager();
156 RepositoryServiceFactory repositoryServiceFactory
= new RepositoryServiceFactory();
157 shutdownHooks
.add(() -> repositoryServiceFactory
.shutdown());
158 bc
.registerService(ManagedServiceFactory
.class, repositoryServiceFactory
,
159 LangUtils
.init(Constants
.SERVICE_PID
, NodeConstants
.JACKRABBIT_FACTORY_PID
));
161 NodeRepositoryFactory repositoryFactory
= new NodeRepositoryFactory();
162 bc
.registerService(RepositoryFactory
.class, repositoryFactory
, null);
164 RepositoryService repositoryService
= new RepositoryService();
165 shutdownHooks
.add(() -> repositoryService
.shutdown());
166 bc
.registerService(LangUtils
.names(ManagedService
.class, MetaTypeProvider
.class), repositoryService
,
167 LangUtils
.init(Constants
.SERVICE_PID
, NodeConstants
.NODE_REPO_PID
));
170 NodeUserAdmin userAdmin
= new NodeUserAdmin();
171 shutdownHooks
.add(() -> userAdmin
.destroy());
172 Dictionary
<String
, Object
> props
= userAdmin
.currentState();
173 props
.put(Constants
.SERVICE_PID
, NodeConstants
.NODE_USER_ADMIN_PID
);
174 bc
.registerService(UserAdmin
.class, userAdmin
, props
);
177 bc
.registerService(ApplicationConfiguration
.class, new MaintenanceUi(),
178 LangUtils
.init(PROPERTY_CONTEXT_NAME
, "system"));
179 bc
.registerService(ApplicationConfiguration
.class, new UserUi(), LangUtils
.init(PROPERTY_CONTEXT_NAME
, "user"));
181 // private void initUserAdmin() {
182 // userAdmin = new NodeUserAdmin();
184 // Dictionary<String, Object> props = userAdmin.currentState();
185 // props.put(Constants.SERVICE_PID, NodeConstants.NODE_USER_ADMIN_PID);
186 // // TODO use ManagedService
187 // bc.registerService(UserAdmin.class, userAdmin, props);
190 private void initTransactionManager() {
191 // TODO manage it in a managed service, as startup could be long
192 ServiceReference
<TransactionManager
> existingTm
= bc
.getServiceReference(TransactionManager
.class);
193 if (existingTm
!= null) {
194 if (log
.isDebugEnabled())
195 log
.debug("Using provided transaction manager " + existingTm
);
197 bitronix
.tm
.Configuration tmConf
= TransactionManagerServices
.getConfiguration();
198 tmConf
.setServerId(UUID
.randomUUID().toString());
200 Bundle bitronixBundle
= FrameworkUtil
.getBundle(bitronix
.tm
.Configuration
.class);
201 File tmBaseDir
= bitronixBundle
.getDataFile(KernelConstants
.DIR_TRANSACTIONS
);
202 File tmDir1
= new File(tmBaseDir
, "btm1");
204 tmConf
.setLogPart1Filename(new File(tmDir1
, tmDir1
.getName() + ".tlog").getAbsolutePath());
205 File tmDir2
= new File(tmBaseDir
, "btm2");
207 tmConf
.setLogPart2Filename(new File(tmDir2
, tmDir2
.getName() + ".tlog").getAbsolutePath());
209 BitronixTransactionManager transactionManager
= getTransactionManager();
210 shutdownHooks
.add(() -> transactionManager
.shutdown());
211 BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry
= getTransactionSynchronizationRegistry();
213 bc
.registerService(TransactionManager
.class, transactionManager
, null);
214 bc
.registerService(UserTransaction
.class, transactionManager
, null);
215 bc
.registerService(TransactionSynchronizationRegistry
.class, transactionSynchronizationRegistry
, null);
216 if (log
.isDebugEnabled())
217 log
.debug("Initialised default Bitronix transaction manager");
220 // private void initRepositoryFactory() {
221 // // TODO rationalise RepositoryFactory
222 // repositoryFactory = new NodeRepositoryFactory();
224 // bc.registerService(RepositoryFactory.class, repositoryFactory, null);
227 // private void initUi() {
228 // bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(),
229 // LangUtils.init(PROPERTY_CONTEXT_NAME, "system"));
230 // bc.registerService(ApplicationConfiguration.class, new UserUi(),
231 // LangUtils.init(PROPERTY_CONTEXT_NAME, "user"));
234 private void initDeployConfigs(Dictionary
<String
, ?
> stateProps
) throws IOException
{
235 Path deployPath
= KernelUtils
.getOsgiInstancePath(KernelConstants
.DIR_NODE
+ '/' + KernelConstants
.DIR_DEPLOY
);
236 Files
.createDirectories(deployPath
);
238 Path nodeConfigPath
= deployPath
.resolve(NodeConstants
.NODE_REPO_PID
+ ".properties");
239 if (!Files
.exists(nodeConfigPath
)) {
240 Dictionary
<String
, Object
> nodeConfig
= getNodeConfig(stateProps
);
241 nodeConfig
.put(ArgeoJcrConstants
.JCR_REPOSITORY_ALIAS
, ArgeoJcrConstants
.ALIAS_NODE
);
242 nodeConfig
.put(RepoConf
.labeledUri
.name(), nodeRepoUri
.toString());
243 LangUtils
.storeAsProperties(nodeConfig
, nodeConfigPath
);
247 try (DirectoryStream
<Path
> ds
= Files
.newDirectoryStream(deployPath
)) {
248 for (Path path
: ds
) {
249 if (Files
.isDirectory(path
)) {// managed factories
250 try (DirectoryStream
<Path
> factoryDs
= Files
.newDirectoryStream(path
)) {
251 for (Path confPath
: factoryDs
) {
252 Configuration conf
= configurationAdmin
253 .createFactoryConfiguration(path
.getFileName().toString());
254 Dictionary
<String
, Object
> props
= LangUtils
.loadFromProperties(confPath
);
258 } else {// managed services
259 String pid
= path
.getFileName().toString();
260 pid
= pid
.substring(0, pid
.length() - ".properties".length());
261 Configuration conf
= configurationAdmin
.getConfiguration(pid
);
262 Dictionary
<String
, Object
> props
= LangUtils
.loadFromProperties(path
);
270 // private void initRepositories(Dictionary<String, ?> stateProps) throws
273 // repositoryServiceFactory = new RepositoryServiceFactory();
274 // bc.registerService(ManagedServiceFactory.class, repositoryServiceFactory,
275 // LangUtils.init(Constants.SERVICE_PID,
276 // NodeConstants.JACKRABBIT_FACTORY_PID));
278 // repositoryService = new RepositoryService();
279 // Dictionary<String, Object> regProps =
280 // LangUtils.init(Constants.SERVICE_PID, NodeConstants.NODE_REPO_PID);
281 // bc.registerService(LangUtils.names(ManagedService.class,
282 // MetaTypeProvider.class), repositoryService, regProps);
285 private void initWebServer() {
286 String httpPort
= getFrameworkProp("org.osgi.service.http.port");
287 String httpsPort
= getFrameworkProp("org.osgi.service.http.port.secure");
289 if (httpPort
!= null || httpsPort
!= null) {
290 final Hashtable
<String
, Object
> jettyProps
= new Hashtable
<String
, Object
>();
291 if (httpPort
!= null) {
292 jettyProps
.put(JettyConstants
.HTTP_PORT
, httpPort
);
293 jettyProps
.put(JettyConstants
.HTTP_ENABLED
, true);
295 if (httpsPort
!= null) {
296 jettyProps
.put(JettyConstants
.HTTPS_PORT
, httpsPort
);
297 jettyProps
.put(JettyConstants
.HTTPS_ENABLED
, true);
298 jettyProps
.put(JettyConstants
.SSL_KEYSTORETYPE
, "PKCS12");
299 // jettyProps.put(JettyConstants.SSL_KEYSTORE,
300 // nodeSecurity.getHttpServerKeyStore().getCanonicalPath());
301 jettyProps
.put(JettyConstants
.SSL_PASSWORD
, "changeit");
302 jettyProps
.put(JettyConstants
.SSL_WANTCLIENTAUTH
, true);
304 if (configurationAdmin
!= null) {
305 // TODO make filter more generic
306 String filter
= "(" + JettyConstants
.HTTP_PORT
+ "=" + httpPort
+ ")";
307 if (configurationAdmin
.listConfigurations(filter
) != null)
309 Configuration jettyConf
= configurationAdmin
310 .createFactoryConfiguration(KernelConstants
.JETTY_FACTORY_PID
, null);
311 jettyConf
.update(jettyProps
);
314 JettyConfigurator
.startServer("default", jettyProps
);
317 } catch (Exception e
) {
318 throw new CmsException("Cannot initialize web server on " + httpPortsMsg(httpPort
, httpsPort
), e
);
322 private void initNodeDeployment() throws IOException
{
323 Configuration nodeDeploymentConf
= configurationAdmin
.getConfiguration(NodeConstants
.NODE_DEPLOYMENT_PID
);
324 nodeDeploymentConf
.update(new Hashtable
<>());
328 // if (transactionManager != null)
329 // transactionManager.shutdown();
330 // if (userAdmin != null)
331 // userAdmin.destroy();
332 // if (repositoryServiceFactory != null)
333 // repositoryServiceFactory.shutdown();
335 applyShutdownHooks();
337 if (kernelThread
!= null)
338 kernelThread
.destroyAndJoin();
340 if (log
.isDebugEnabled())
341 log
.debug("## CMS STOPPED");
344 /** Apply shutdown hoos in reverse order. */
345 private void applyShutdownHooks() {
346 for (int i
= shutdownHooks
.size() - 1; i
>= 0; i
--) {
348 // new Thread(shutdownHooks.get(i), "CMS Shutdown Hook #" +
350 shutdownHooks
.get(i
).run();
351 } catch (Exception e
) {
352 log
.error("Could not run shutdown hook #" + i
);
355 // Clean hanging Gogo shell thread
356 new GogoShellKiller().start();
359 private Dictionary
<String
, Object
> getNodeConfig(Dictionary
<String
, ?
> properties
) {
360 // Object repoType = properties.get(NodeConstants.NODE_REPO_PROP_PREFIX
361 // + RepoConf.type.name());
362 // if (repoType == null)
365 Hashtable
<String
, Object
> props
= new Hashtable
<String
, Object
>();
366 for (RepoConf repoConf
: RepoConf
.values()) {
367 Object value
= properties
.get(NodeConstants
.NODE_REPO_PROP_PREFIX
+ repoConf
.name());
369 props
.put(repoConf
.name(), value
);
374 private class RepositoryContextStc
implements ServiceTrackerCustomizer
<RepositoryContext
, RepositoryContext
> {
377 public RepositoryContext
addingService(ServiceReference
<RepositoryContext
> reference
) {
378 RepositoryContext nodeRepo
= bc
.getService(reference
);
379 Object repoUri
= reference
.getProperty(ArgeoJcrConstants
.JCR_REPOSITORY_URI
);
380 if (repoUri
!= null && repoUri
.equals(nodeRepoUri
.toString())) {
381 nodeDeployment
.setDeployedNodeRepository(nodeRepo
.getRepository());
382 Dictionary
<String
, Object
> props
= LangUtils
.init(Constants
.SERVICE_PID
,
383 NodeConstants
.NODE_DEPLOYMENT_PID
);
384 props
.put(NodeConstants
.CN
, nodeRepo
.getRootNodeId().toString());
386 bc
.registerService(LangUtils
.names(NodeDeployment
.class, ManagedService
.class), nodeDeployment
, props
);
393 public void modifiedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {
397 public void removedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {
402 private class PrepareHttpStc
implements ServiceTrackerCustomizer
<HttpService
, HttpService
> {
403 private DataHttp dataHttp
;
404 private NodeHttp nodeHttp
;
407 public HttpService
addingService(ServiceReference
<HttpService
> reference
) {
408 HttpService httpService
= addHttpService(reference
);
413 public void modifiedService(ServiceReference
<HttpService
> reference
, HttpService service
) {
417 public void removedService(ServiceReference
<HttpService
> reference
, HttpService service
) {
418 if (dataHttp
!= null)
421 if (nodeHttp
!= null)
426 private HttpService
addHttpService(ServiceReference
<HttpService
> sr
) {
427 HttpService httpService
= bc
.getService(sr
);
428 // TODO find constants
429 Object httpPort
= sr
.getProperty("http.port");
430 Object httpsPort
= sr
.getProperty("https.port");
431 dataHttp
= new DataHttp(httpService
);
432 nodeHttp
= new NodeHttp(httpService
, bc
);
433 if (log
.isDebugEnabled())
434 log
.debug(httpPortsMsg(httpPort
, httpsPort
));
443 public Locale
getDefaultLocale() {
444 return defaultLocale
;
447 public List
<Locale
> getLocales() {
451 public String
getHostname() {
458 private static String
httpPortsMsg(Object httpPort
, Object httpsPort
) {
459 return "HTTP " + httpPort
+ (httpsPort
!= null ?
" - HTTPS " + httpsPort
: "");
462 /** Workaround for blocking Gogo shell by system shutdown. */
463 private class GogoShellKiller
extends Thread
{
465 public GogoShellKiller() {
466 super("Gogo Shell Killer");
472 ThreadGroup rootTg
= getRootThreadGroup(null);
473 Thread gogoShellThread
= findGogoShellThread(rootTg
);
474 if (gogoShellThread
== null)
476 while (getNonDaemonCount(rootTg
) > 2) {
479 } catch (InterruptedException e
) {
483 gogoShellThread
= findGogoShellThread(rootTg
);
484 if (gogoShellThread
== null)
490 private static ThreadGroup
getRootThreadGroup(ThreadGroup tg
) {
492 tg
= Thread
.currentThread().getThreadGroup();
493 if (tg
.getParent() == null)
496 return getRootThreadGroup(tg
.getParent());
499 private static int getNonDaemonCount(ThreadGroup rootThreadGroup
) {
500 Thread
[] threads
= new Thread
[rootThreadGroup
.activeCount()];
501 rootThreadGroup
.enumerate(threads
);
502 int nonDameonCount
= 0;
503 for (Thread t
: threads
)
504 if (t
!= null && !t
.isDaemon())
506 return nonDameonCount
;
509 private static Thread
findGogoShellThread(ThreadGroup rootThreadGroup
) {
510 Thread
[] threads
= new Thread
[rootThreadGroup
.activeCount()];
511 rootThreadGroup
.enumerate(threads
, true);
512 for (Thread thread
: threads
) {
513 if (thread
.getName().equals("Gogo shell"))