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