]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java
Test of ident client (commented out for the time being)
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / CmsDeployment.java
1 package org.argeo.cms.internal.kernel;
2
3 import static org.argeo.node.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
4
5 import java.io.File;
6 import java.io.InputStreamReader;
7 import java.io.Reader;
8 import java.lang.management.ManagementFactory;
9 import java.net.URL;
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.Hashtable;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16
17 import javax.jcr.Repository;
18 import javax.jcr.Session;
19 import javax.security.auth.callback.CallbackHandler;
20 import javax.transaction.UserTransaction;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.apache.jackrabbit.commons.cnd.CndImporter;
25 import org.apache.jackrabbit.core.RepositoryContext;
26 import org.apache.jackrabbit.core.RepositoryImpl;
27 import org.argeo.cms.CmsException;
28 import org.argeo.jcr.JcrUtils;
29 import org.argeo.node.DataModelNamespace;
30 import org.argeo.node.NodeConstants;
31 import org.argeo.node.NodeDeployment;
32 import org.argeo.node.NodeState;
33 import org.argeo.node.security.CryptoKeyring;
34 import org.argeo.node.security.Keyring;
35 import org.argeo.osgi.useradmin.UserAdminConf;
36 import org.argeo.util.LangUtils;
37 import org.osgi.framework.Bundle;
38 import org.osgi.framework.BundleContext;
39 import org.osgi.framework.Constants;
40 import org.osgi.framework.FrameworkUtil;
41 import org.osgi.framework.ServiceReference;
42 import org.osgi.framework.wiring.BundleCapability;
43 import org.osgi.framework.wiring.BundleWire;
44 import org.osgi.framework.wiring.BundleWiring;
45 import org.osgi.service.cm.Configuration;
46 import org.osgi.service.cm.ConfigurationAdmin;
47 import org.osgi.service.cm.ManagedService;
48 import org.osgi.service.useradmin.Group;
49 import org.osgi.service.useradmin.Role;
50 import org.osgi.service.useradmin.UserAdmin;
51 import org.osgi.util.tracker.ServiceTracker;
52
53 public class CmsDeployment implements NodeDeployment {
54 // private final static String LEGACY_JCR_REPOSITORY_ALIAS =
55 // "argeo.jcr.repository.alias";
56
57 private final Log log = LogFactory.getLog(getClass());
58 private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
59
60 private DataModels dataModels;
61 private DeployConfig deployConfig;
62 private HomeRepository homeRepository;
63
64 private Long availableSince;
65
66 private final boolean cleanState;
67
68 private NodeHttp nodeHttp;
69
70 // Readiness
71 private boolean nodeAvailable = false;
72 private boolean userAdminAvailable = false;
73 private boolean httpExpected = false;
74 private boolean httpAvailable = false;
75
76 public CmsDeployment() {
77 ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
78 if (nodeStateSr == null)
79 throw new CmsException("No node state available");
80
81 NodeState nodeState = bc.getService(nodeStateSr);
82 cleanState = nodeState.isClean();
83
84 nodeHttp = new NodeHttp(cleanState);
85 dataModels = new DataModels(bc);
86 initTrackers();
87 }
88
89 private void initTrackers() {
90 ServiceTracker<?, ?> httpSt = new ServiceTracker<NodeHttp, NodeHttp>(bc, NodeHttp.class, null) {
91
92 @Override
93 public NodeHttp addingService(ServiceReference<NodeHttp> reference) {
94 httpAvailable = true;
95 checkReadiness();
96 return super.addingService(reference);
97 }
98 };
99 // httpSt.open();
100 KernelUtils.asyncOpen(httpSt);
101
102 ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
103 // repoContextSt.open();
104 KernelUtils.asyncOpen(repoContextSt);
105
106 ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
107 @Override
108 public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
109 UserAdmin userAdmin = super.addingService(reference);
110 addStandardSystemRoles(userAdmin);
111 userAdminAvailable = true;
112 checkReadiness();
113 return userAdmin;
114 }
115 };
116 // userAdminSt.open();
117 KernelUtils.asyncOpen(userAdminSt);
118
119 ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
120 ConfigurationAdmin.class, null) {
121 @Override
122 public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
123 ConfigurationAdmin configurationAdmin = bc.getService(reference);
124 deployConfig = new DeployConfig(configurationAdmin, dataModels, cleanState);
125 httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
126 try {
127 // Configuration[] configs = configurationAdmin
128 // .listConfigurations("(service.factoryPid=" +
129 // NodeConstants.NODE_REPOS_FACTORY_PID + ")");
130 // for (Configuration config : configs) {
131 // Object cn = config.getProperties().get(NodeConstants.CN);
132 // if (log.isDebugEnabled())
133 // log.debug("Standalone repo cn: " + cn);
134 // }
135 Configuration[] configs = configurationAdmin
136 .listConfigurations("(service.factoryPid=" + NodeConstants.NODE_USER_ADMIN_PID + ")");
137
138 boolean hasDomain = false;
139 for (Configuration config : configs) {
140 Object realm = config.getProperties().get(UserAdminConf.realm.name());
141 if (realm != null) {
142 log.debug("Found realm: " + realm);
143 hasDomain = true;
144 }
145 }
146 if (hasDomain) {
147 loadIpaJaasConfiguration();
148 }
149 } catch (Exception e) {
150 throw new CmsException("Cannot initialize config", e);
151 }
152 return super.addingService(reference);
153 }
154 };
155 // confAdminSt.open();
156 KernelUtils.asyncOpen(confAdminSt);
157 }
158
159 private void addStandardSystemRoles(UserAdmin userAdmin) {
160 // we assume UserTransaction is already available (TODO make it more robust)
161 UserTransaction userTransaction = bc.getService(bc.getServiceReference(UserTransaction.class));
162 try {
163 userTransaction.begin();
164 Role adminRole = userAdmin.getRole(NodeConstants.ROLE_ADMIN);
165 if (adminRole == null) {
166 adminRole = userAdmin.createRole(NodeConstants.ROLE_ADMIN, Role.GROUP);
167 }
168 if (userAdmin.getRole(NodeConstants.ROLE_USER_ADMIN) == null) {
169 Group userAdminRole = (Group) userAdmin.createRole(NodeConstants.ROLE_USER_ADMIN, Role.GROUP);
170 userAdminRole.addMember(adminRole);
171 }
172 userTransaction.commit();
173 } catch (Exception e) {
174 try {
175 userTransaction.rollback();
176 } catch (Exception e1) {
177 // silent
178 }
179 throw new CmsException("Cannot add standard system roles", e);
180 }
181 }
182
183 private void loadIpaJaasConfiguration() {
184 if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
185 String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
186 URL url = getClass().getClassLoader().getResource(jaasConfig);
187 KernelUtils.setJaasConfiguration(url);
188 log.debug("Set IPA JAAS configuration.");
189 }
190 }
191
192 public void shutdown() {
193 if (nodeHttp != null)
194 nodeHttp.destroy();
195 if (deployConfig != null) {
196 new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
197 }
198 }
199
200 /**
201 * Checks whether the deployment is available according to expectations, and
202 * mark it as available.
203 */
204 private synchronized void checkReadiness() {
205 if (isAvailable())
206 return;
207 if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
208 String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
209 String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
210 availableSince = System.currentTimeMillis();
211 long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
212 String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
213 log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
214 if (log.isDebugEnabled()) {
215 log.debug("## state: " + state);
216 if (data != null)
217 log.debug("## data: " + data);
218 }
219 long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
220 long initDuration = System.currentTimeMillis() - begin;
221 if (log.isTraceEnabled())
222 log.trace("Kernel initialization took " + initDuration + "ms");
223 tributeToFreeSoftware(initDuration);
224 }
225 }
226
227 final private void tributeToFreeSoftware(long initDuration) {
228 if (log.isTraceEnabled()) {
229 long ms = initDuration / 100;
230 log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
231 long beginNano = System.nanoTime();
232 try {
233 Thread.sleep(ms, 0);
234 } catch (InterruptedException e) {
235 // silent
236 }
237 long durationNano = System.nanoTime() - beginNano;
238 final double M = 1000d * 1000d;
239 double sleepAccuracy = ((double) durationNano) / (ms * M);
240 log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
241 }
242 }
243
244 private void prepareNodeRepository(Repository deployedNodeRepository) {
245 if (availableSince != null) {
246 throw new CmsException("Deployment is already available");
247 }
248
249 // home
250 prepareDataModel(NodeConstants.NODE, KernelUtils.openAdminSession(deployedNodeRepository));
251 }
252
253 private void prepareHomeRepository(RepositoryImpl deployedRepository) {
254 Hashtable<String, String> regProps = new Hashtable<String, String>();
255 regProps.put(NodeConstants.CN, NodeConstants.HOME);
256 // regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME);
257 homeRepository = new HomeRepository(deployedRepository, false);
258 // register
259 bc.registerService(Repository.class, homeRepository, regProps);
260
261 new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
262
263 @Override
264 public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
265 NodeKeyRing nodeKeyring = new NodeKeyRing(homeRepository);
266 CallbackHandler callbackHandler = bc.getService(reference);
267 nodeKeyring.setDefaultCallbackHandler(callbackHandler);
268 bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
269 nodeKeyring, LangUtils.dico(Constants.SERVICE_PID, NodeConstants.NODE_KEYRING_PID));
270 return callbackHandler;
271 }
272
273 }.open();
274 }
275
276 /** Session is logged out. */
277 private void prepareDataModel(String cn, Session adminSession) {
278 try {
279 Set<String> processed = new HashSet<String>();
280 bundles: for (Bundle bundle : bc.getBundles()) {
281 BundleWiring wiring = bundle.adapt(BundleWiring.class);
282 if (wiring == null)
283 continue bundles;
284 if (NodeConstants.NODE.equals(cn))// process all data models
285 processWiring(cn, adminSession, wiring, processed);
286 else {
287 List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
288 for (BundleCapability capability : capabilities) {
289 String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
290 if (dataModelName.equals(cn))// process only own data model
291 processWiring(cn, adminSession, wiring, processed);
292 }
293 }
294 }
295 } finally {
296 JcrUtils.logoutQuietly(adminSession);
297 }
298 }
299
300 private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed) {
301 // recursively process requirements first
302 List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
303 for (BundleWire wire : requiredWires) {
304 processWiring(cn, adminSession, wire.getProviderWiring(), processed);
305 }
306
307 List<String> publishAsLocalRepo = new ArrayList<>();
308 List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
309 for (BundleCapability capability : capabilities) {
310 boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
311 if (publish)
312 publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
313 }
314 // Publish all at once, so that bundles with multiple CNDs are consistent
315 for (String dataModelName : publishAsLocalRepo)
316 publishLocalRepo(dataModelName, adminSession.getRepository());
317 }
318
319 private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
320 Set<String> processed) {
321 Map<String, Object> attrs = capability.getAttributes();
322 String name = (String) attrs.get(DataModelNamespace.NAME);
323 if (processed.contains(name)) {
324 if (log.isTraceEnabled())
325 log.trace("Data model " + name + " has already been processed");
326 return false;
327 }
328
329 // CND
330 String path = (String) attrs.get(DataModelNamespace.CND);
331 if (path != null) {
332 File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
333 if (!dataModel.exists()) {
334 URL url = capability.getRevision().getBundle().getResource(path);
335 if (url == null)
336 throw new CmsException("No data model '" + name + "' found under path " + path);
337 try (Reader reader = new InputStreamReader(url.openStream())) {
338 CndImporter.registerNodeTypes(reader, adminSession, true);
339 processed.add(name);
340 dataModel.getParentFile().mkdirs();
341 dataModel.createNewFile();
342 if (log.isDebugEnabled())
343 log.debug("Registered CND " + url);
344 } catch (Exception e) {
345 throw new CmsException("Cannot import CND " + url, e);
346 }
347 }
348 }
349
350 if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
351 return false;
352 // Non abstract
353 boolean isStandalone = deployConfig.isStandalone(name);
354 boolean publishLocalRepo;
355 if (isStandalone && name.equals(cn))// includes the node itself
356 publishLocalRepo = true;
357 else if (!isStandalone && cn.equals(NodeConstants.NODE))
358 publishLocalRepo = true;
359 else
360 publishLocalRepo = false;
361
362 return publishLocalRepo;
363 }
364
365 private void publishLocalRepo(String dataModelName, Repository repository) {
366 Hashtable<String, Object> properties = new Hashtable<>();
367 // properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name);
368 properties.put(NodeConstants.CN, dataModelName);
369 if (dataModelName.equals(NodeConstants.NODE))
370 properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
371 LocalRepository localRepository = new LocalRepository(repository, dataModelName);
372 bc.registerService(Repository.class, localRepository, properties);
373 if (log.isTraceEnabled())
374 log.trace("Published data model " + dataModelName);
375 }
376
377 @Override
378 public synchronized Long getAvailableSince() {
379 return availableSince;
380 }
381
382 public synchronized boolean isAvailable() {
383 return availableSince != null;
384 }
385
386 private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
387
388 public RepositoryContextStc() {
389 super(bc, RepositoryContext.class, null);
390 }
391
392 @Override
393 public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
394 RepositoryContext repoContext = bc.getService(reference);
395 String cn = (String) reference.getProperty(NodeConstants.CN);
396 if (cn != null) {
397 if (cn.equals(NodeConstants.NODE)) {
398 prepareNodeRepository(repoContext.getRepository());
399 // TODO separate home repository
400 prepareHomeRepository(repoContext.getRepository());
401 nodeAvailable = true;
402 checkReadiness();
403 } else {
404 prepareDataModel(cn, KernelUtils.openAdminSession(repoContext.getRepository()));
405 }
406 }
407 return repoContext;
408 }
409
410 @Override
411 public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
412 }
413
414 @Override
415 public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
416 }
417
418 }
419
420 }