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