1 package org
.argeo
.cms
.internal
.kernel
;
3 import static org
.argeo
.api
.DataModelNamespace
.CMS_DATA_MODEL_NAMESPACE
;
6 import java
.io
.IOException
;
7 import java
.io
.InputStreamReader
;
9 import java
.lang
.management
.ManagementFactory
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Arrays
;
13 import java
.util
.HashSet
;
14 import java
.util
.Hashtable
;
15 import java
.util
.List
;
19 import javax
.jcr
.Repository
;
20 import javax
.jcr
.RepositoryException
;
21 import javax
.jcr
.Session
;
22 import javax
.security
.auth
.callback
.CallbackHandler
;
23 import javax
.transaction
.UserTransaction
;
25 import org
.apache
.commons
.logging
.Log
;
26 import org
.apache
.commons
.logging
.LogFactory
;
27 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
28 import org
.apache
.jackrabbit
.core
.RepositoryContext
;
29 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
30 import org
.argeo
.api
.DataModelNamespace
;
31 import org
.argeo
.api
.NodeConstants
;
32 import org
.argeo
.api
.NodeDeployment
;
33 import org
.argeo
.api
.NodeState
;
34 import org
.argeo
.api
.security
.CryptoKeyring
;
35 import org
.argeo
.api
.security
.Keyring
;
36 import org
.argeo
.cms
.ArgeoNames
;
37 import org
.argeo
.cms
.CmsException
;
38 import org
.argeo
.jcr
.JcrUtils
;
39 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
40 import org
.argeo
.util
.LangUtils
;
41 import org
.eclipse
.equinox
.http
.jetty
.JettyConfigurator
;
42 import org
.osgi
.framework
.Bundle
;
43 import org
.osgi
.framework
.BundleContext
;
44 import org
.osgi
.framework
.Constants
;
45 import org
.osgi
.framework
.FrameworkUtil
;
46 import org
.osgi
.framework
.InvalidSyntaxException
;
47 import org
.osgi
.framework
.ServiceReference
;
48 import org
.osgi
.framework
.wiring
.BundleCapability
;
49 import org
.osgi
.framework
.wiring
.BundleWire
;
50 import org
.osgi
.framework
.wiring
.BundleWiring
;
51 import org
.osgi
.service
.cm
.Configuration
;
52 import org
.osgi
.service
.cm
.ConfigurationAdmin
;
53 import org
.osgi
.service
.cm
.ManagedService
;
54 import org
.osgi
.service
.useradmin
.Group
;
55 import org
.osgi
.service
.useradmin
.Role
;
56 import org
.osgi
.service
.useradmin
.UserAdmin
;
57 import org
.osgi
.util
.tracker
.ServiceTracker
;
59 /** Implementation of a CMS deployment. */
60 public class CmsDeployment
implements NodeDeployment
{
61 private final Log log
= LogFactory
.getLog(getClass());
62 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
64 private DataModels dataModels
;
65 private DeployConfig deployConfig
;
67 private Long availableSince
;
69 // private final boolean cleanState;
71 private NodeHttp nodeHttp
;
73 private boolean argeoDataModelExtensionsAvailable
= false;
76 private boolean nodeAvailable
= false;
77 private boolean userAdminAvailable
= false;
78 private boolean httpExpected
= false;
79 private boolean httpAvailable
= false;
81 public CmsDeployment() {
82 // ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
83 // if (nodeStateSr == null)
84 // throw new CmsException("No node state available");
86 // NodeState nodeState = bc.getService(nodeStateSr);
87 // cleanState = nodeState.isClean();
89 nodeHttp
= new NodeHttp();
90 dataModels
= new DataModels(bc
);
94 private void initTrackers() {
95 ServiceTracker
<?
, ?
> httpSt
= new ServiceTracker
<NodeHttp
, NodeHttp
>(bc
, NodeHttp
.class, null) {
98 public NodeHttp
addingService(ServiceReference
<NodeHttp
> reference
) {
101 return super.addingService(reference
);
105 KernelUtils
.asyncOpen(httpSt
);
107 ServiceTracker
<?
, ?
> repoContextSt
= new RepositoryContextStc();
108 // repoContextSt.open();
109 KernelUtils
.asyncOpen(repoContextSt
);
111 ServiceTracker
<?
, ?
> userAdminSt
= new ServiceTracker
<UserAdmin
, UserAdmin
>(bc
, UserAdmin
.class, null) {
113 public UserAdmin
addingService(ServiceReference
<UserAdmin
> reference
) {
114 UserAdmin userAdmin
= super.addingService(reference
);
115 addStandardSystemRoles(userAdmin
);
116 userAdminAvailable
= true;
121 // userAdminSt.open();
122 KernelUtils
.asyncOpen(userAdminSt
);
124 ServiceTracker
<?
, ?
> confAdminSt
= new ServiceTracker
<ConfigurationAdmin
, ConfigurationAdmin
>(bc
,
125 ConfigurationAdmin
.class, null) {
127 public ConfigurationAdmin
addingService(ServiceReference
<ConfigurationAdmin
> reference
) {
128 ConfigurationAdmin configurationAdmin
= bc
.getService(reference
);
131 Configuration
[] confs
= configurationAdmin
132 .listConfigurations("(service.factoryPid=" + NodeConstants
.NODE_USER_ADMIN_PID
+ ")");
133 isClean
= confs
== null || confs
.length
== 0;
134 } catch (Exception e
) {
135 throw new CmsException("Cannot analize clean state", e
);
137 deployConfig
= new DeployConfig(configurationAdmin
, dataModels
, isClean
);
138 httpExpected
= deployConfig
.getProps(KernelConstants
.JETTY_FACTORY_PID
, "default") != null;
140 Configuration
[] configs
= configurationAdmin
141 .listConfigurations("(service.factoryPid=" + NodeConstants
.NODE_USER_ADMIN_PID
+ ")");
143 boolean hasDomain
= false;
144 for (Configuration config
: configs
) {
145 Object realm
= config
.getProperties().get(UserAdminConf
.realm
.name());
147 log
.debug("Found realm: " + realm
);
152 loadIpaJaasConfiguration();
154 } catch (Exception e
) {
155 throw new CmsException("Cannot initialize config", e
);
157 return super.addingService(reference
);
160 // confAdminSt.open();
161 KernelUtils
.asyncOpen(confAdminSt
);
164 private void addStandardSystemRoles(UserAdmin userAdmin
) {
165 // we assume UserTransaction is already available (TODO make it more robust)
166 UserTransaction userTransaction
= bc
.getService(bc
.getServiceReference(UserTransaction
.class));
168 userTransaction
.begin();
169 Role adminRole
= userAdmin
.getRole(NodeConstants
.ROLE_ADMIN
);
170 if (adminRole
== null) {
171 adminRole
= userAdmin
.createRole(NodeConstants
.ROLE_ADMIN
, Role
.GROUP
);
173 if (userAdmin
.getRole(NodeConstants
.ROLE_USER_ADMIN
) == null) {
174 Group userAdminRole
= (Group
) userAdmin
.createRole(NodeConstants
.ROLE_USER_ADMIN
, Role
.GROUP
);
175 userAdminRole
.addMember(adminRole
);
177 userTransaction
.commit();
178 } catch (Exception e
) {
180 userTransaction
.rollback();
181 } catch (Exception e1
) {
184 throw new CmsException("Cannot add standard system roles", e
);
188 private void loadIpaJaasConfiguration() {
189 if (System
.getProperty(KernelConstants
.JAAS_CONFIG_PROP
) == null) {
190 String jaasConfig
= KernelConstants
.JAAS_CONFIG_IPA
;
191 URL url
= getClass().getClassLoader().getResource(jaasConfig
);
192 KernelUtils
.setJaasConfiguration(url
);
193 log
.debug("Set IPA JAAS configuration.");
197 public void shutdown() {
198 if (nodeHttp
!= null)
202 for (ServiceReference
<JackrabbitLocalRepository
> sr
: bc
203 .getServiceReferences(JackrabbitLocalRepository
.class, null)) {
204 bc
.getService(sr
).destroy();
206 } catch (InvalidSyntaxException e1
) {
207 log
.error("Cannot sclean repsoitories", e1
);
211 JettyConfigurator
.stopServer(KernelConstants
.DEFAULT_JETTY_SERVER
);
212 } catch (Exception e
) {
213 log
.error("Cannot stop default Jetty server.", e
);
216 if (deployConfig
!= null) {
217 new Thread(() -> deployConfig
.save(), "Save Argeo Deploy Config").start();
222 * Checks whether the deployment is available according to expectations, and
223 * mark it as available.
225 private synchronized void checkReadiness() {
228 if (nodeAvailable
&& userAdminAvailable
&& (httpExpected ? httpAvailable
: true)) {
229 String data
= KernelUtils
.getFrameworkProp(KernelUtils
.OSGI_INSTANCE_AREA
);
230 String state
= KernelUtils
.getFrameworkProp(KernelUtils
.OSGI_CONFIGURATION_AREA
);
231 availableSince
= System
.currentTimeMillis();
232 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
233 String jvmUptimeStr
= " in " + (jvmUptime
/ 1000) + "." + (jvmUptime
% 1000) + "s";
234 log
.info("## ARGEO NODE AVAILABLE" + (log
.isDebugEnabled() ? jvmUptimeStr
: "") + " ##");
235 if (log
.isDebugEnabled()) {
236 log
.debug("## state: " + state
);
238 log
.debug("## data: " + data
);
240 long begin
= bc
.getService(bc
.getServiceReference(NodeState
.class)).getAvailableSince();
241 long initDuration
= System
.currentTimeMillis() - begin
;
242 if (log
.isTraceEnabled())
243 log
.trace("Kernel initialization took " + initDuration
+ "ms");
244 tributeToFreeSoftware(initDuration
);
248 final private void tributeToFreeSoftware(long initDuration
) {
249 if (log
.isTraceEnabled()) {
250 long ms
= initDuration
/ 100;
251 log
.trace("Spend " + ms
+ "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
252 long beginNano
= System
.nanoTime();
255 } catch (InterruptedException e
) {
258 long durationNano
= System
.nanoTime() - beginNano
;
259 final double M
= 1000d
* 1000d
;
260 double sleepAccuracy
= ((double) durationNano
) / (ms
* M
);
261 log
.trace("Sleep accuracy: " + String
.format("%.2f", 100 - (sleepAccuracy
* 100 - 100)) + " %");
265 private void prepareNodeRepository(Repository deployedNodeRepository
) {
266 if (availableSince
!= null) {
267 throw new CmsException("Deployment is already available");
271 prepareDataModel(NodeConstants
.NODE_REPOSITORY
, deployedNodeRepository
);
274 private void prepareHomeRepository(RepositoryImpl deployedRepository
) {
275 Session adminSession
= KernelUtils
.openAdminSession(deployedRepository
);
277 argeoDataModelExtensionsAvailable
= Arrays
278 .asList(adminSession
.getWorkspace().getNamespaceRegistry().getURIs())
279 .contains(ArgeoNames
.ARGEO_NAMESPACE
);
280 } catch (RepositoryException e
) {
281 log
.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e
);
282 argeoDataModelExtensionsAvailable
= false;
284 JcrUtils
.logoutQuietly(adminSession
);
287 // Publish home with the highest service ranking
288 Hashtable
<String
, Object
> regProps
= new Hashtable
<>();
289 regProps
.put(NodeConstants
.CN
, NodeConstants
.EGO_REPOSITORY
);
290 regProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
291 Repository egoRepository
= new EgoRepository(deployedRepository
, false);
292 bc
.registerService(Repository
.class, egoRepository
, regProps
);
294 // Keyring only if Argeo extensions are available
295 if (argeoDataModelExtensionsAvailable
) {
296 new ServiceTracker
<CallbackHandler
, CallbackHandler
>(bc
, CallbackHandler
.class, null) {
299 public CallbackHandler
addingService(ServiceReference
<CallbackHandler
> reference
) {
300 NodeKeyRing nodeKeyring
= new NodeKeyRing(egoRepository
);
301 CallbackHandler callbackHandler
= bc
.getService(reference
);
302 nodeKeyring
.setDefaultCallbackHandler(callbackHandler
);
303 bc
.registerService(LangUtils
.names(Keyring
.class, CryptoKeyring
.class, ManagedService
.class),
304 nodeKeyring
, LangUtils
.dico(Constants
.SERVICE_PID
, NodeConstants
.NODE_KEYRING_PID
));
305 return callbackHandler
;
312 /** Session is logged out. */
313 private void prepareDataModel(String cn
, Repository repository
) {
314 Session adminSession
= KernelUtils
.openAdminSession(repository
);
316 Set
<String
> processed
= new HashSet
<String
>();
317 bundles
: for (Bundle bundle
: bc
.getBundles()) {
318 BundleWiring wiring
= bundle
.adapt(BundleWiring
.class);
321 if (NodeConstants
.NODE_REPOSITORY
.equals(cn
))// process all data models
322 processWiring(cn
, adminSession
, wiring
, processed
, false);
324 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
325 for (BundleCapability capability
: capabilities
) {
326 String dataModelName
= (String
) capability
.getAttributes().get(DataModelNamespace
.NAME
);
327 if (dataModelName
.equals(cn
))// process only own data model
328 processWiring(cn
, adminSession
, wiring
, processed
, false);
333 JcrUtils
.logoutQuietly(adminSession
);
337 private void processWiring(String cn
, Session adminSession
, BundleWiring wiring
, Set
<String
> processed
,
338 boolean importListedAbstractModels
) {
339 // recursively process requirements first
340 List
<BundleWire
> requiredWires
= wiring
.getRequiredWires(CMS_DATA_MODEL_NAMESPACE
);
341 for (BundleWire wire
: requiredWires
) {
342 processWiring(cn
, adminSession
, wire
.getProviderWiring(), processed
, true);
345 List
<String
> publishAsLocalRepo
= new ArrayList
<>();
346 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
347 capabilities
: for (BundleCapability capability
: capabilities
) {
348 if (!importListedAbstractModels
349 && KernelUtils
.asBoolean((String
) capability
.getAttributes().get(DataModelNamespace
.ABSTRACT
))) {
350 continue capabilities
;
352 boolean publish
= registerDataModelCapability(cn
, adminSession
, capability
, processed
);
354 publishAsLocalRepo
.add((String
) capability
.getAttributes().get(DataModelNamespace
.NAME
));
356 // Publish all at once, so that bundles with multiple CNDs are consistent
357 for (String dataModelName
: publishAsLocalRepo
)
358 publishLocalRepo(dataModelName
, adminSession
.getRepository());
361 private boolean registerDataModelCapability(String cn
, Session adminSession
, BundleCapability capability
,
362 Set
<String
> processed
) {
363 Map
<String
, Object
> attrs
= capability
.getAttributes();
364 String name
= (String
) attrs
.get(DataModelNamespace
.NAME
);
365 if (processed
.contains(name
)) {
366 if (log
.isTraceEnabled())
367 log
.trace("Data model " + name
+ " has already been processed");
372 String path
= (String
) attrs
.get(DataModelNamespace
.CND
);
374 File dataModel
= bc
.getBundle().getDataFile("dataModels/" + path
);
375 if (!dataModel
.exists()) {
376 URL url
= capability
.getRevision().getBundle().getResource(path
);
378 throw new CmsException("No data model '" + name
+ "' found under path " + path
);
379 try (Reader reader
= new InputStreamReader(url
.openStream())) {
380 CndImporter
.registerNodeTypes(reader
, adminSession
, true);
382 dataModel
.getParentFile().mkdirs();
383 dataModel
.createNewFile();
384 if (log
.isDebugEnabled())
385 log
.debug("Registered CND " + url
);
386 } catch (Exception e
) {
387 throw new CmsException("Cannot import CND " + url
, e
);
392 if (KernelUtils
.asBoolean((String
) attrs
.get(DataModelNamespace
.ABSTRACT
)))
395 boolean isStandalone
= deployConfig
.isStandalone(name
);
396 boolean publishLocalRepo
;
397 if (isStandalone
&& name
.equals(cn
))// includes the node itself
398 publishLocalRepo
= true;
399 else if (!isStandalone
&& cn
.equals(NodeConstants
.NODE_REPOSITORY
))
400 publishLocalRepo
= true;
402 publishLocalRepo
= false;
404 return publishLocalRepo
;
407 private void publishLocalRepo(String dataModelName
, Repository repository
) {
408 Hashtable
<String
, Object
> properties
= new Hashtable
<>();
409 properties
.put(NodeConstants
.CN
, dataModelName
);
410 LocalRepository localRepository
;
412 if (repository
instanceof RepositoryImpl
) {
413 localRepository
= new JackrabbitLocalRepository((RepositoryImpl
) repository
, dataModelName
);
414 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName(),
415 JackrabbitLocalRepository
.class.getName() };
417 localRepository
= new LocalRepository(repository
, dataModelName
);
418 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName() };
420 bc
.registerService(classes
, localRepository
, properties
);
421 if (log
.isTraceEnabled())
422 log
.trace("Published data model " + dataModelName
);
426 public synchronized Long
getAvailableSince() {
427 return availableSince
;
430 public synchronized boolean isAvailable() {
431 return availableSince
!= null;
434 private class RepositoryContextStc
extends ServiceTracker
<RepositoryContext
, RepositoryContext
> {
436 public RepositoryContextStc() {
437 super(bc
, RepositoryContext
.class, null);
441 public RepositoryContext
addingService(ServiceReference
<RepositoryContext
> reference
) {
442 RepositoryContext repoContext
= bc
.getService(reference
);
443 String cn
= (String
) reference
.getProperty(NodeConstants
.CN
);
445 if (cn
.equals(NodeConstants
.NODE_REPOSITORY
)) {
446 prepareNodeRepository(repoContext
.getRepository());
447 // TODO separate home repository
448 prepareHomeRepository(repoContext
.getRepository());
449 nodeAvailable
= true;
452 prepareDataModel(cn
, repoContext
.getRepository());
459 public void modifiedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {
463 public void removedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {