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