1 package org
.argeo
.cms
.internal
.kernel
;
3 import static org
.argeo
.node
.DataModelNamespace
.CMS_DATA_MODEL_NAMESPACE
;
6 import java
.io
.InputStreamReader
;
8 import java
.lang
.management
.ManagementFactory
;
10 import java
.util
.ArrayList
;
11 import java
.util
.Arrays
;
12 import java
.util
.HashSet
;
13 import java
.util
.Hashtable
;
14 import java
.util
.List
;
18 import javax
.jcr
.Repository
;
19 import javax
.jcr
.RepositoryException
;
20 import javax
.jcr
.Session
;
21 import javax
.security
.auth
.callback
.CallbackHandler
;
22 import javax
.transaction
.UserTransaction
;
24 import org
.apache
.commons
.logging
.Log
;
25 import org
.apache
.commons
.logging
.LogFactory
;
26 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
27 import org
.apache
.jackrabbit
.core
.RepositoryContext
;
28 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
29 import org
.argeo
.cms
.ArgeoNames
;
30 import org
.argeo
.cms
.CmsException
;
31 import org
.argeo
.jcr
.JcrUtils
;
32 import org
.argeo
.node
.DataModelNamespace
;
33 import org
.argeo
.node
.NodeConstants
;
34 import org
.argeo
.node
.NodeDeployment
;
35 import org
.argeo
.node
.NodeState
;
36 import org
.argeo
.node
.security
.CryptoKeyring
;
37 import org
.argeo
.node
.security
.Keyring
;
38 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
39 import org
.argeo
.util
.LangUtils
;
40 import org
.eclipse
.equinox
.http
.jetty
.JettyConfigurator
;
41 import org
.osgi
.framework
.Bundle
;
42 import org
.osgi
.framework
.BundleContext
;
43 import org
.osgi
.framework
.Constants
;
44 import org
.osgi
.framework
.FrameworkUtil
;
45 import org
.osgi
.framework
.InvalidSyntaxException
;
46 import org
.osgi
.framework
.ServiceReference
;
47 import org
.osgi
.framework
.wiring
.BundleCapability
;
48 import org
.osgi
.framework
.wiring
.BundleWire
;
49 import org
.osgi
.framework
.wiring
.BundleWiring
;
50 import org
.osgi
.service
.cm
.Configuration
;
51 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
52 import org
.osgi
.service
.cm
.ManagedService
;
53 import org
.osgi
.service
.useradmin
.Group
;
54 import org
.osgi
.service
.useradmin
.Role
;
55 import org
.osgi
.service
.useradmin
.UserAdmin
;
56 import org
.osgi
.util
.tracker
.ServiceTracker
;
58 public class CmsDeployment
implements NodeDeployment
{
59 // private final static String LEGACY_JCR_REPOSITORY_ALIAS =
60 // "argeo.jcr.repository.alias";
62 private final Log log
= LogFactory
.getLog(getClass());
63 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
65 private DataModels dataModels
;
66 private DeployConfig deployConfig
;
67 private HomeRepository homeRepository
;
69 private Long availableSince
;
71 private final boolean cleanState
;
73 private NodeHttp nodeHttp
;
75 private boolean argeoDataModelExtensionsAvailable
= false;
78 private boolean nodeAvailable
= false;
79 private boolean userAdminAvailable
= false;
80 private boolean httpExpected
= false;
81 private boolean httpAvailable
= false;
83 public CmsDeployment() {
84 ServiceReference
<NodeState
> nodeStateSr
= bc
.getServiceReference(NodeState
.class);
85 if (nodeStateSr
== null)
86 throw new CmsException("No node state available");
88 NodeState nodeState
= bc
.getService(nodeStateSr
);
89 cleanState
= nodeState
.isClean();
91 nodeHttp
= new NodeHttp(cleanState
);
92 dataModels
= new DataModels(bc
);
96 private void initTrackers() {
97 ServiceTracker
<?
, ?
> httpSt
= new ServiceTracker
<NodeHttp
, NodeHttp
>(bc
, NodeHttp
.class, null) {
100 public NodeHttp
addingService(ServiceReference
<NodeHttp
> reference
) {
101 httpAvailable
= true;
103 return super.addingService(reference
);
107 KernelUtils
.asyncOpen(httpSt
);
109 ServiceTracker
<?
, ?
> repoContextSt
= new RepositoryContextStc();
110 // repoContextSt.open();
111 KernelUtils
.asyncOpen(repoContextSt
);
113 ServiceTracker
<?
, ?
> userAdminSt
= new ServiceTracker
<UserAdmin
, UserAdmin
>(bc
, UserAdmin
.class, null) {
115 public UserAdmin
addingService(ServiceReference
<UserAdmin
> reference
) {
116 UserAdmin userAdmin
= super.addingService(reference
);
117 addStandardSystemRoles(userAdmin
);
118 userAdminAvailable
= true;
123 // userAdminSt.open();
124 KernelUtils
.asyncOpen(userAdminSt
);
126 ServiceTracker
<?
, ?
> confAdminSt
= new ServiceTracker
<ConfigurationAdmin
, ConfigurationAdmin
>(bc
,
127 ConfigurationAdmin
.class, null) {
129 public ConfigurationAdmin
addingService(ServiceReference
<ConfigurationAdmin
> reference
) {
130 ConfigurationAdmin configurationAdmin
= bc
.getService(reference
);
131 deployConfig
= new DeployConfig(configurationAdmin
, dataModels
, cleanState
);
132 httpExpected
= deployConfig
.getProps(KernelConstants
.JETTY_FACTORY_PID
, "default") != null;
134 // Configuration[] configs = configurationAdmin
135 // .listConfigurations("(service.factoryPid=" +
136 // NodeConstants.NODE_REPOS_FACTORY_PID + ")");
137 // for (Configuration config : configs) {
138 // Object cn = config.getProperties().get(NodeConstants.CN);
139 // if (log.isDebugEnabled())
140 // log.debug("Standalone repo cn: " + cn);
142 Configuration
[] configs
= configurationAdmin
143 .listConfigurations("(service.factoryPid=" + NodeConstants
.NODE_USER_ADMIN_PID
+ ")");
145 boolean hasDomain
= false;
146 for (Configuration config
: configs
) {
147 Object realm
= config
.getProperties().get(UserAdminConf
.realm
.name());
149 log
.debug("Found realm: " + realm
);
154 loadIpaJaasConfiguration();
156 } catch (Exception e
) {
157 throw new CmsException("Cannot initialize config", e
);
159 return super.addingService(reference
);
162 // confAdminSt.open();
163 KernelUtils
.asyncOpen(confAdminSt
);
166 private void addStandardSystemRoles(UserAdmin userAdmin
) {
167 // we assume UserTransaction is already available (TODO make it more robust)
168 UserTransaction userTransaction
= bc
.getService(bc
.getServiceReference(UserTransaction
.class));
170 userTransaction
.begin();
171 Role adminRole
= userAdmin
.getRole(NodeConstants
.ROLE_ADMIN
);
172 if (adminRole
== null) {
173 adminRole
= userAdmin
.createRole(NodeConstants
.ROLE_ADMIN
, Role
.GROUP
);
175 if (userAdmin
.getRole(NodeConstants
.ROLE_USER_ADMIN
) == null) {
176 Group userAdminRole
= (Group
) userAdmin
.createRole(NodeConstants
.ROLE_USER_ADMIN
, Role
.GROUP
);
177 userAdminRole
.addMember(adminRole
);
179 userTransaction
.commit();
180 } catch (Exception e
) {
182 userTransaction
.rollback();
183 } catch (Exception e1
) {
186 throw new CmsException("Cannot add standard system roles", e
);
190 private void loadIpaJaasConfiguration() {
191 if (System
.getProperty(KernelConstants
.JAAS_CONFIG_PROP
) == null) {
192 String jaasConfig
= KernelConstants
.JAAS_CONFIG_IPA
;
193 URL url
= getClass().getClassLoader().getResource(jaasConfig
);
194 KernelUtils
.setJaasConfiguration(url
);
195 log
.debug("Set IPA JAAS configuration.");
199 public void shutdown() {
200 if (nodeHttp
!= null)
204 for (ServiceReference
<JackrabbitLocalRepository
> sr
: bc
205 .getServiceReferences(JackrabbitLocalRepository
.class, null)) {
206 bc
.getService(sr
).destroy();
208 } catch (InvalidSyntaxException e1
) {
209 log
.error("Cannot sclean repsoitories", e1
);
213 JettyConfigurator
.stopServer(KernelConstants
.DEFAULT_JETTY_SERVER
);
214 } catch (Exception e
) {
215 log
.error("Cannot stop default Jetty server.", e
);
218 if (deployConfig
!= null) {
219 new Thread(() -> deployConfig
.save(), "Save Argeo Deploy Config").start();
224 * Checks whether the deployment is available according to expectations, and
225 * mark it as available.
227 private synchronized void checkReadiness() {
230 if (nodeAvailable
&& userAdminAvailable
&& (httpExpected ? httpAvailable
: true)) {
231 String data
= KernelUtils
.getFrameworkProp(KernelUtils
.OSGI_INSTANCE_AREA
);
232 String state
= KernelUtils
.getFrameworkProp(KernelUtils
.OSGI_CONFIGURATION_AREA
);
233 availableSince
= System
.currentTimeMillis();
234 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
235 String jvmUptimeStr
= " in " + (jvmUptime
/ 1000) + "." + (jvmUptime
% 1000) + "s";
236 log
.info("## ARGEO NODE AVAILABLE" + (log
.isDebugEnabled() ? jvmUptimeStr
: "") + " ##");
237 if (log
.isDebugEnabled()) {
238 log
.debug("## state: " + state
);
240 log
.debug("## data: " + data
);
242 long begin
= bc
.getService(bc
.getServiceReference(NodeState
.class)).getAvailableSince();
243 long initDuration
= System
.currentTimeMillis() - begin
;
244 if (log
.isTraceEnabled())
245 log
.trace("Kernel initialization took " + initDuration
+ "ms");
246 tributeToFreeSoftware(initDuration
);
250 final private void tributeToFreeSoftware(long initDuration
) {
251 if (log
.isTraceEnabled()) {
252 long ms
= initDuration
/ 100;
253 log
.trace("Spend " + ms
+ "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
254 long beginNano
= System
.nanoTime();
257 } catch (InterruptedException e
) {
260 long durationNano
= System
.nanoTime() - beginNano
;
261 final double M
= 1000d
* 1000d
;
262 double sleepAccuracy
= ((double) durationNano
) / (ms
* M
);
263 log
.trace("Sleep accuracy: " + String
.format("%.2f", 100 - (sleepAccuracy
* 100 - 100)) + " %");
267 private void prepareNodeRepository(Repository deployedNodeRepository
) {
268 if (availableSince
!= null) {
269 throw new CmsException("Deployment is already available");
273 prepareDataModel(NodeConstants
.NODE
, deployedNodeRepository
);
276 private void prepareHomeRepository(RepositoryImpl deployedRepository
) {
277 Session adminSession
= KernelUtils
.openAdminSession(deployedRepository
);
279 argeoDataModelExtensionsAvailable
= Arrays
280 .asList(adminSession
.getWorkspace().getNamespaceRegistry().getURIs())
281 .contains(ArgeoNames
.ARGEO_NAMESPACE
);
282 } catch (RepositoryException e
) {
283 log
.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e
);
284 argeoDataModelExtensionsAvailable
= false;
286 JcrUtils
.logoutQuietly(adminSession
);
289 Hashtable
<String
, String
> regProps
= new Hashtable
<String
, String
>();
290 regProps
.put(NodeConstants
.CN
, NodeConstants
.HOME
);
291 // regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME);
292 homeRepository
= new HomeRepository(deployedRepository
, false);
294 bc
.registerService(Repository
.class, homeRepository
, regProps
);
296 // Keyring only if Argeo extensions are available
297 if (argeoDataModelExtensionsAvailable
) {
298 new ServiceTracker
<CallbackHandler
, CallbackHandler
>(bc
, CallbackHandler
.class, null) {
301 public CallbackHandler
addingService(ServiceReference
<CallbackHandler
> reference
) {
302 NodeKeyRing nodeKeyring
= new NodeKeyRing(homeRepository
);
303 CallbackHandler callbackHandler
= bc
.getService(reference
);
304 nodeKeyring
.setDefaultCallbackHandler(callbackHandler
);
305 bc
.registerService(LangUtils
.names(Keyring
.class, CryptoKeyring
.class, ManagedService
.class),
306 nodeKeyring
, LangUtils
.dico(Constants
.SERVICE_PID
, NodeConstants
.NODE_KEYRING_PID
));
307 return callbackHandler
;
314 /** Session is logged out. */
315 private void prepareDataModel(String cn
, Repository repository
) {
316 Session adminSession
= KernelUtils
.openAdminSession(repository
);
318 Set
<String
> processed
= new HashSet
<String
>();
319 bundles
: for (Bundle bundle
: bc
.getBundles()) {
320 BundleWiring wiring
= bundle
.adapt(BundleWiring
.class);
323 if (NodeConstants
.NODE
.equals(cn
))// process all data models
324 processWiring(cn
, adminSession
, wiring
, processed
, false);
326 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
327 for (BundleCapability capability
: capabilities
) {
328 String dataModelName
= (String
) capability
.getAttributes().get(DataModelNamespace
.NAME
);
329 if (dataModelName
.equals(cn
))// process only own data model
330 processWiring(cn
, adminSession
, wiring
, processed
, false);
335 JcrUtils
.logoutQuietly(adminSession
);
339 private void processWiring(String cn
, Session adminSession
, BundleWiring wiring
, Set
<String
> processed
,
340 boolean importListedAbstractModels
) {
341 // recursively process requirements first
342 List
<BundleWire
> requiredWires
= wiring
.getRequiredWires(CMS_DATA_MODEL_NAMESPACE
);
343 for (BundleWire wire
: requiredWires
) {
344 processWiring(cn
, adminSession
, wire
.getProviderWiring(), processed
, true);
347 List
<String
> publishAsLocalRepo
= new ArrayList
<>();
348 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
349 capabilities
: for (BundleCapability capability
: capabilities
) {
350 if (!importListedAbstractModels
351 && KernelUtils
.asBoolean((String
) capability
.getAttributes().get(DataModelNamespace
.ABSTRACT
))) {
352 continue capabilities
;
354 boolean publish
= registerDataModelCapability(cn
, adminSession
, capability
, processed
);
356 publishAsLocalRepo
.add((String
) capability
.getAttributes().get(DataModelNamespace
.NAME
));
358 // Publish all at once, so that bundles with multiple CNDs are consistent
359 for (String dataModelName
: publishAsLocalRepo
)
360 publishLocalRepo(dataModelName
, adminSession
.getRepository());
363 private boolean registerDataModelCapability(String cn
, Session adminSession
, BundleCapability capability
,
364 Set
<String
> processed
) {
365 Map
<String
, Object
> attrs
= capability
.getAttributes();
366 String name
= (String
) attrs
.get(DataModelNamespace
.NAME
);
367 if (processed
.contains(name
)) {
368 if (log
.isTraceEnabled())
369 log
.trace("Data model " + name
+ " has already been processed");
374 String path
= (String
) attrs
.get(DataModelNamespace
.CND
);
376 File dataModel
= bc
.getBundle().getDataFile("dataModels/" + path
);
377 if (!dataModel
.exists()) {
378 URL url
= capability
.getRevision().getBundle().getResource(path
);
380 throw new CmsException("No data model '" + name
+ "' found under path " + path
);
381 try (Reader reader
= new InputStreamReader(url
.openStream())) {
382 CndImporter
.registerNodeTypes(reader
, adminSession
, true);
384 dataModel
.getParentFile().mkdirs();
385 dataModel
.createNewFile();
386 if (log
.isDebugEnabled())
387 log
.debug("Registered CND " + url
);
388 } catch (Exception e
) {
389 throw new CmsException("Cannot import CND " + url
, e
);
394 if (KernelUtils
.asBoolean((String
) attrs
.get(DataModelNamespace
.ABSTRACT
)))
397 boolean isStandalone
= deployConfig
.isStandalone(name
);
398 boolean publishLocalRepo
;
399 if (isStandalone
&& name
.equals(cn
))// includes the node itself
400 publishLocalRepo
= true;
401 else if (!isStandalone
&& cn
.equals(NodeConstants
.NODE
))
402 publishLocalRepo
= true;
404 publishLocalRepo
= false;
406 return publishLocalRepo
;
409 private void publishLocalRepo(String dataModelName
, Repository repository
) {
410 Hashtable
<String
, Object
> properties
= new Hashtable
<>();
411 // properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name);
412 properties
.put(NodeConstants
.CN
, dataModelName
);
413 if (dataModelName
.equals(NodeConstants
.NODE
))
414 properties
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
415 LocalRepository localRepository
;
417 if (repository
instanceof RepositoryImpl
) {
418 localRepository
= new JackrabbitLocalRepository((RepositoryImpl
) repository
, dataModelName
);
419 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName(),
420 JackrabbitLocalRepository
.class.getName() };
422 localRepository
= new LocalRepository(repository
, dataModelName
);
423 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName() };
425 bc
.registerService(classes
, localRepository
, properties
);
426 if (log
.isTraceEnabled())
427 log
.trace("Published data model " + dataModelName
);
431 public synchronized Long
getAvailableSince() {
432 return availableSince
;
435 public synchronized boolean isAvailable() {
436 return availableSince
!= null;
439 private class RepositoryContextStc
extends ServiceTracker
<RepositoryContext
, RepositoryContext
> {
441 public RepositoryContextStc() {
442 super(bc
, RepositoryContext
.class, null);
446 public RepositoryContext
addingService(ServiceReference
<RepositoryContext
> reference
) {
447 RepositoryContext repoContext
= bc
.getService(reference
);
448 String cn
= (String
) reference
.getProperty(NodeConstants
.CN
);
450 if (cn
.equals(NodeConstants
.NODE
)) {
451 prepareNodeRepository(repoContext
.getRepository());
452 // TODO separate home repository
453 prepareHomeRepository(repoContext
.getRepository());
454 nodeAvailable
= true;
457 prepareDataModel(cn
, repoContext
.getRepository());
464 public void modifiedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {
468 public void removedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {