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