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