]> git.argeo.org Git - lgpl/argeo-commons.git/blob - DeployConfig.java
e45b9508d19026133631e6af48d6a299118af7f2
[lgpl/argeo-commons.git] / DeployConfig.java
1 package org.argeo.cms.internal.osgi;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.Writer;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.Dictionary;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Set;
14 import java.util.SortedMap;
15 import java.util.TreeMap;
16
17 import javax.naming.InvalidNameException;
18 import javax.naming.directory.Attribute;
19 import javax.naming.directory.Attributes;
20 import javax.naming.directory.BasicAttributes;
21 import javax.naming.ldap.LdapName;
22 import javax.naming.ldap.Rdn;
23
24 import org.argeo.api.cms.CmsConstants;
25 import org.argeo.api.cms.CmsLog;
26 import org.argeo.cms.internal.runtime.InitUtils;
27 import org.argeo.cms.internal.runtime.KernelConstants;
28 import org.argeo.cms.internal.runtime.KernelUtils;
29 import org.argeo.osgi.useradmin.UserAdminConf;
30 import org.argeo.util.naming.AttributesDictionary;
31 import org.argeo.util.naming.LdifParser;
32 import org.argeo.util.naming.LdifWriter;
33 import org.osgi.framework.InvalidSyntaxException;
34 import org.osgi.service.cm.Configuration;
35 import org.osgi.service.cm.ConfigurationAdmin;
36 import org.osgi.service.cm.ConfigurationEvent;
37
38 /** Manages the LDIF-based deployment configuration. */
39 public class DeployConfig {
40
41 private final CmsLog log = CmsLog.getLog(getClass());
42 // private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
43
44 private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
45 private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
46 // private final DataModels dataModels;
47
48 private boolean isFirstInit = false;
49
50 private final static String ROLES = "roles";
51
52 private ConfigurationAdmin configurationAdmin;
53
54 private void firstInit() throws IOException {
55 log.info("## FIRST INIT ##");
56 Files.createDirectories(deployConfigPath.getParent());
57
58 // FirstInit firstInit = new FirstInit();
59 InitUtils.prepareFirstInitInstanceArea();
60
61 if (!Files.exists(deployConfigPath))
62 deployConfigs = new TreeMap<>();
63 else// config file could have juste been copied by preparation
64 try (InputStream in = Files.newInputStream(deployConfigPath)) {
65 deployConfigs = new LdifParser().read(in);
66 }
67 save();
68 }
69
70 private void setFromFrameworkProperties(boolean isFirstInit) {
71
72 // user admin
73 List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
74 if (userDirectoryConfigs.size() != 0) {
75 List<String> activeCns = new ArrayList<>();
76 for (int i = 0; i < userDirectoryConfigs.size(); i++) {
77 Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
78 String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
79 String cn;
80 if (CmsConstants.ROLES_BASEDN.equals(baseDn))
81 cn = ROLES;
82 else
83 cn = UserAdminConf.baseDnHash(userDirectoryConfig);
84 activeCns.add(cn);
85 userDirectoryConfig.put(CmsConstants.CN, cn);
86 putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
87 }
88 // disable others
89 LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID);
90 for (LdapName name : deployConfigs.keySet()) {
91 if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
92 // try {
93 Attributes attrs = deployConfigs.get(name);
94 String cn = name.getRdn(name.size() - 1).getValue().toString();
95 if (!activeCns.contains(cn)) {
96 attrs.put(UserAdminConf.disabled.name(), "true");
97 }
98 // } catch (Exception e) {
99 // throw new CmsException("Cannot disable user directory " + name, e);
100 // }
101 }
102 }
103 }
104
105 // http server
106 Dictionary<String, Object> webServerConfig = InitUtils
107 .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
108 if (!webServerConfig.isEmpty()) {
109 // TODO check for other customizers
110 // webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
111 putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
112 }
113 // LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT);
114 // if (deployConfigs.containsKey(defaultHttpServiceDn)) {
115 // // remove old default configs since we have now to start Jetty servlet bridge
116 // // indirectly
117 // deployConfigs.remove(defaultHttpServiceDn);
118 // }
119
120 // SAVE
121 save();
122 //
123
124 // Dictionary<String, Object> webServerConfig = InitUtils
125 // .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
126 }
127
128 public void start() {
129 try {
130 if (!isInitialized()) { // first init
131 isFirstInit = true;
132 firstInit();
133 }
134
135 boolean isClean = true;
136 if (configurationAdmin != null)
137 try {
138 Configuration[] confs = configurationAdmin
139 .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
140 isClean = confs == null || confs.length == 0;
141 } catch (Exception e) {
142 throw new IllegalStateException("Cannot analyse clean state", e);
143 }
144
145 try (InputStream in = Files.newInputStream(deployConfigPath)) {
146 deployConfigs = new LdifParser().read(in);
147 }
148 if (isClean) {
149 if (log.isDebugEnabled())
150 log.debug("Clean state, loading from framework properties...");
151 setFromFrameworkProperties(isFirstInit);
152 if (configurationAdmin != null)
153 loadConfigs();
154 }
155 // TODO check consistency if not clean
156 } catch (IOException e) {
157 throw new RuntimeException("Cannot load deploy configuration", e);
158 }
159 }
160
161 public void stop() {
162
163 }
164
165 protected void logAllConfigurations() {
166 if (!log.isDebugEnabled())
167 return;
168 try {
169 Configuration[] configurations = configurationAdmin.listConfigurations(null);
170 if (configurations == null) {
171 log.debug("No configuration available");
172 return;
173 }
174 Arrays.sort(configurations, (o1, o2) -> o1.getPid().compareTo(o2.getPid()));
175 for (Configuration configuration : configurations) {
176 log.debug(configuration.getFactoryPid() + " - " + configuration.getPid() + " - "
177 + configuration.getProperties());
178 }
179 } catch (IOException | InvalidSyntaxException e) {
180 throw new IllegalStateException("Cannot log configurations", e);
181 }
182 }
183
184 public void loadConfigs() throws IOException {
185 // FIXME make it more robust
186 Configuration systemRolesConf = null;
187 LdapName systemRolesDn;
188 try {
189 // FIXME make it more robust
190 systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
191 } catch (InvalidNameException e) {
192 throw new IllegalArgumentException(e);
193 }
194 deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
195 Attributes deployConfig = deployConfigs.get(dn);
196 Rdn lastRdn = dn.getRdn(dn.size() - 1);
197 LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
198 if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
199 if (lastRdn.getType().equals(CmsConstants.CN)) {
200 // service
201 String pid = lastRdn.getValue().toString();
202 Configuration conf = configurationAdmin.getConfiguration(pid);
203 AttributesDictionary dico = new AttributesDictionary(deployConfig);
204 conf.update(dico);
205 } else {
206 // service factory definition
207 }
208 } else {
209 Attribute disabled = deployConfig.get(UserAdminConf.disabled.name());
210 if (disabled != null)
211 continue deployConfigs;
212 // service factory service
213 if (!lastRdn.getType().equals(CmsConstants.CN))
214 throw new IllegalStateException("Only " + CmsConstants.CN + "= is supported: " + dn);
215 Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
216 assert beforeLastRdn.getType().equals(CmsConstants.OU);
217 String factoryPid = beforeLastRdn.getValue().toString();
218
219 String cn = lastRdn.getValue().toString();
220 Configuration conf = getSingleServiceConfiguration(factoryPid, cn);
221 if (conf != null) {
222 if (systemRolesDn.equals(dn))
223 systemRolesConf = conf;
224 // TODO deal with modifications
225 // boolean modified = false;
226 // Dictionary<String, Object> currentProperties = conf.getProperties();
227 //
228 // attrs: for (NamingEnumeration<? extends Attribute> it = deployConfig.getAll(); it
229 // .hasMoreElements();) {
230 // Attribute attr = (Attribute) it.next();
231 // String key = attr.getID();
232 // Object currentValue = currentProperties.get(key);
233 // if (currentValue == null) {
234 // modified = true;
235 // break attrs;
236 // }
237 // }
238
239 // AttributesDictionary dico = new AttributesDictionary(deployConfig);
240 // conf.update(dico);
241 } else {
242
243 conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
244 if (systemRolesDn.equals(dn)) {
245 systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
246 } else {
247 AttributesDictionary dico = new AttributesDictionary(deployConfig);
248 conf.update(dico);
249 }
250 }
251 }
252 }
253
254 // system roles must be last since it triggers node user admin publication
255 if (systemRolesConf == null)
256 throw new IllegalStateException("System roles are not configured.");
257 systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
258
259 // logAllConfigurations();
260 }
261
262 public Set<Dictionary<String, Object>> getUserDirectoryConfigs() {
263 // not static because class is not supported by Android
264 final LdapName USER_ADMIN_BASE_DN;
265 try {
266 USER_ADMIN_BASE_DN = new LdapName(
267 CmsConstants.OU + "=" + CmsConstants.NODE_USER_ADMIN_PID + "," + CmsConstants.DEPLOY_BASEDN);
268 } catch (InvalidNameException e) {
269 throw new IllegalArgumentException(e);
270 }
271 Set<Dictionary<String, Object>> res = new HashSet<>();
272 for (LdapName dn : deployConfigs.keySet()) {
273 if (dn.endsWith(USER_ADMIN_BASE_DN)) {
274 Attributes attributes = deployConfigs.get(dn);
275 res.add(new AttributesDictionary(attributes));
276 }
277 }
278 return res;
279 }
280
281 // @Override
282 public void configurationEvent(ConfigurationEvent event) {
283 try {
284 if (ConfigurationEvent.CM_UPDATED == event.getType()) {
285 Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
286 LdapName serviceDn = null;
287 String factoryPid = conf.getFactoryPid();
288 if (factoryPid != null) {
289 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
290 if (deployConfigs.containsKey(serviceFactoryDn)) {
291 for (LdapName dn : deployConfigs.keySet()) {
292 if (dn.startsWith(serviceFactoryDn)) {
293 Rdn lastRdn = dn.getRdn(dn.size() - 1);
294 assert lastRdn.getType().equals(CmsConstants.CN);
295 Object value = conf.getProperties().get(lastRdn.getType());
296 assert value != null;
297 if (value.equals(lastRdn.getValue())) {
298 serviceDn = dn;
299 break;
300 }
301 }
302 }
303
304 Object cn = conf.getProperties().get(CmsConstants.CN);
305 if (cn == null)
306 throw new IllegalArgumentException("Properties must contain cn");
307 if (serviceDn == null) {
308 putFactoryDeployConfig(factoryPid, conf.getProperties());
309 } else {
310 Attributes attrs = deployConfigs.get(serviceDn);
311 assert attrs != null;
312 AttributesDictionary.copy(conf.getProperties(), attrs);
313 }
314 save();
315 if (log.isDebugEnabled())
316 log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
317 } else {
318 // ignore non config-registered service factories
319 }
320 } else {
321 serviceDn = serviceDn(event.getPid());
322 if (deployConfigs.containsKey(serviceDn)) {
323 Attributes attrs = deployConfigs.get(serviceDn);
324 assert attrs != null;
325 AttributesDictionary.copy(conf.getProperties(), attrs);
326 save();
327 if (log.isDebugEnabled())
328 log.debug("Updated deploy config " + serviceDn);
329 } else {
330 // ignore non config-registered services
331 }
332 }
333 }
334 } catch (Exception e) {
335 log.error("Could not handle configuration event", e);
336 }
337 }
338
339 public void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
340 Object cn = props.get(CmsConstants.CN);
341 if (cn == null)
342 throw new IllegalArgumentException("cn must be set in properties");
343 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
344 if (!deployConfigs.containsKey(serviceFactoryDn))
345 deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
346 LdapName serviceDn = serviceDn(factoryPid, cn.toString());
347 Attributes attrs = new BasicAttributes();
348 AttributesDictionary.copy(props, attrs);
349 deployConfigs.put(serviceDn, attrs);
350 }
351
352 void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
353 LdapName serviceDn = serviceDn(servicePid);
354 Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
355 AttributesDictionary.copy(props, attrs);
356 deployConfigs.put(serviceDn, attrs);
357 }
358
359 public void save() {
360 try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
361 new LdifWriter(writer).write(deployConfigs);
362 } catch (IOException e) {
363 // throw new CmsException("Cannot save deploy configs", e);
364 log.error("Cannot save deploy configs", e);
365 }
366 }
367
368 public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
369 this.configurationAdmin = configurationAdmin;
370 }
371
372 public boolean hasDomain() {
373 // FIXME lookup deploy configs directly
374 if (configurationAdmin == null)
375 return false;
376
377 Configuration[] configs = listConfigurationsByFactory(CmsConstants.NODE_USER_ADMIN_PID);
378
379 boolean hasDomain = false;
380 for (Configuration config : configs) {
381 Object realm = config.getProperties().get(UserAdminConf.realm.name());
382 if (realm != null) {
383 log.debug("Found realm: " + realm);
384 hasDomain = true;
385 }
386 }
387 return hasDomain;
388 }
389
390 private Configuration[] listConfigurationsByFactory(String factoryPid) {
391 try {
392 Configuration[] configs = configurationAdmin.listConfigurations("(service.factoryPid=" + factoryPid + ")");
393 if (configs == null)
394 configs = new Configuration[0];
395 return configs;
396 } catch (IOException | InvalidSyntaxException e) {
397 throw new IllegalStateException("Cannot list configurations with factoryPid " + factoryPid, e);
398 }
399
400 }
401
402 private Configuration getSingleServiceConfiguration(String factoryPid, String cn) {
403 Configuration[] configs = listConfigurationsByFactory(factoryPid);
404 List<Configuration> res = new ArrayList<>();
405 for (Configuration config : configs) {
406 Object currentCn = config.getProperties().get(CmsConstants.CN);
407 if (currentCn != null && cn.equals(currentCn.toString()))
408 res.add(config);
409 }
410 if (res.size() == 0)
411 return null;
412 if (res.size() > 1)
413 throw new IllegalStateException(
414 "More than one " + factoryPid + " configuration returned for " + CmsConstants.CN + "=" + cn);
415 return res.get(0);
416 }
417
418 /*
419 * UTILITIES
420 */
421 private LdapName serviceFactoryDn(String factoryPid) {
422 try {
423 return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
424 } catch (InvalidNameException e) {
425 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
426 }
427 }
428
429 private LdapName serviceDn(String servicePid) {
430 try {
431 return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
432 } catch (InvalidNameException e) {
433 throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
434 }
435 }
436
437 private LdapName serviceDn(String factoryPid, String cn) {
438 try {
439 return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
440 } catch (InvalidNameException e) {
441 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
442 }
443 }
444
445 public Dictionary<String, Object> getProps(String factoryPid, String cn) {
446 Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
447 if (attrs != null)
448 return new AttributesDictionary(attrs);
449 else
450 return null;
451 }
452
453 private static boolean isInitialized() {
454 return Files.exists(deployConfigPath);
455 }
456
457 public boolean isFirstInit() {
458 return isFirstInit;
459 }
460
461 }