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