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