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