]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java
Introduce static CMS.
[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.Dictionary;
10 import java.util.HashSet;
11 import java.util.List;
12 import java.util.Set;
13 import java.util.SortedMap;
14 import java.util.TreeMap;
15
16 import javax.naming.InvalidNameException;
17 import javax.naming.directory.Attribute;
18 import javax.naming.directory.Attributes;
19 import javax.naming.directory.BasicAttributes;
20 import javax.naming.ldap.LdapName;
21 import javax.naming.ldap.Rdn;
22
23 import org.argeo.api.cms.CmsConstants;
24 import org.argeo.api.cms.CmsLog;
25 import org.argeo.cms.internal.runtime.InitUtils;
26 import org.argeo.cms.internal.runtime.KernelConstants;
27 import org.argeo.cms.internal.runtime.KernelUtils;
28 import org.argeo.osgi.useradmin.UserAdminConf;
29 import org.argeo.util.naming.AttributesDictionary;
30 import org.argeo.util.naming.LdifParser;
31 import org.argeo.util.naming.LdifWriter;
32 import org.osgi.framework.InvalidSyntaxException;
33 import org.osgi.service.cm.Configuration;
34 import org.osgi.service.cm.ConfigurationAdmin;
35 import org.osgi.service.cm.ConfigurationEvent;
36 import org.osgi.service.cm.ConfigurationListener;
37
38 /** Manages the LDIF-based deployment configuration. */
39 public class DeployConfig implements ConfigurationListener {
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 public void loadConfigs() throws IOException {
175 // FIXME make it more robust
176 Configuration systemRolesConf = null;
177 LdapName systemRolesDn;
178 try {
179 // FIXME make it more robust
180 systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
181 } catch (InvalidNameException e) {
182 throw new IllegalArgumentException(e);
183 }
184 deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
185 Rdn lastRdn = dn.getRdn(dn.size() - 1);
186 LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
187 if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
188 if (lastRdn.getType().equals(CmsConstants.CN)) {
189 // service
190 String pid = lastRdn.getValue().toString();
191 Configuration conf = configurationAdmin.getConfiguration(pid);
192 AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
193 conf.update(dico);
194 } else {
195 // service factory definition
196 }
197 } else {
198 Attributes config = deployConfigs.get(dn);
199 Attribute disabled = config.get(UserAdminConf.disabled.name());
200 if (disabled != null)
201 continue deployConfigs;
202 // service factory service
203 Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
204 assert beforeLastRdn.getType().equals(CmsConstants.OU);
205 String factoryPid = beforeLastRdn.getValue().toString();
206 Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
207 if (systemRolesDn.equals(dn)) {
208 systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
209 } else {
210 AttributesDictionary dico = new AttributesDictionary(config);
211 conf.update(dico);
212 }
213 }
214 }
215
216 // system roles must be last since it triggers node user admin publication
217 if (systemRolesConf == null)
218 throw new IllegalStateException("System roles are not configured.");
219 systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
220
221 }
222
223 public Set<Dictionary<String, Object>> getUserDirectoryConfigs() {
224 Set<Dictionary<String, Object>> res = new HashSet<>();
225 for (LdapName dn : deployConfigs.keySet()) {
226 if (dn.endsWith(USER_ADMIN_BASE_DN)) {
227 Attributes attributes = deployConfigs.get(dn);
228 res.add(new AttributesDictionary(attributes));
229 }
230 }
231 return res;
232 }
233
234 @Override
235 public void configurationEvent(ConfigurationEvent event) {
236 try {
237 if (ConfigurationEvent.CM_UPDATED == event.getType()) {
238 Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
239 LdapName serviceDn = null;
240 String factoryPid = conf.getFactoryPid();
241 if (factoryPid != null) {
242 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
243 if (deployConfigs.containsKey(serviceFactoryDn)) {
244 for (LdapName dn : deployConfigs.keySet()) {
245 if (dn.startsWith(serviceFactoryDn)) {
246 Rdn lastRdn = dn.getRdn(dn.size() - 1);
247 assert lastRdn.getType().equals(CmsConstants.CN);
248 Object value = conf.getProperties().get(lastRdn.getType());
249 assert value != null;
250 if (value.equals(lastRdn.getValue())) {
251 serviceDn = dn;
252 break;
253 }
254 }
255 }
256
257 Object cn = conf.getProperties().get(CmsConstants.CN);
258 if (cn == null)
259 throw new IllegalArgumentException("Properties must contain cn");
260 if (serviceDn == null) {
261 putFactoryDeployConfig(factoryPid, conf.getProperties());
262 } else {
263 Attributes attrs = deployConfigs.get(serviceDn);
264 assert attrs != null;
265 AttributesDictionary.copy(conf.getProperties(), attrs);
266 }
267 save();
268 if (log.isDebugEnabled())
269 log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
270 } else {
271 // ignore non config-registered service factories
272 }
273 } else {
274 serviceDn = serviceDn(event.getPid());
275 if (deployConfigs.containsKey(serviceDn)) {
276 Attributes attrs = deployConfigs.get(serviceDn);
277 assert attrs != null;
278 AttributesDictionary.copy(conf.getProperties(), attrs);
279 save();
280 if (log.isDebugEnabled())
281 log.debug("Updated deploy config " + serviceDn);
282 } else {
283 // ignore non config-registered services
284 }
285 }
286 }
287 } catch (Exception e) {
288 log.error("Could not handle configuration event", e);
289 }
290 }
291
292 public void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
293 Object cn = props.get(CmsConstants.CN);
294 if (cn == null)
295 throw new IllegalArgumentException("cn must be set in properties");
296 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
297 if (!deployConfigs.containsKey(serviceFactoryDn))
298 deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
299 LdapName serviceDn = serviceDn(factoryPid, cn.toString());
300 Attributes attrs = new BasicAttributes();
301 AttributesDictionary.copy(props, attrs);
302 deployConfigs.put(serviceDn, attrs);
303 }
304
305 void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
306 LdapName serviceDn = serviceDn(servicePid);
307 Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
308 AttributesDictionary.copy(props, attrs);
309 deployConfigs.put(serviceDn, attrs);
310 }
311
312 public void save() {
313 try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
314 new LdifWriter(writer).write(deployConfigs);
315 } catch (IOException e) {
316 // throw new CmsException("Cannot save deploy configs", e);
317 log.error("Cannot save deploy configs", e);
318 }
319 }
320
321 public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
322 this.configurationAdmin = configurationAdmin;
323 }
324
325 public boolean hasDomain() {
326 // FIXME lookup deploy configs directly
327 if (configurationAdmin == null)
328 return false;
329
330 Configuration[] configs;
331 try {
332 configs = configurationAdmin
333 .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
334 } catch (IOException | InvalidSyntaxException e) {
335 throw new IllegalStateException("Cannot list user directories", e);
336 }
337
338 boolean hasDomain = false;
339 for (Configuration config : configs) {
340 Object realm = config.getProperties().get(UserAdminConf.realm.name());
341 if (realm != null) {
342 log.debug("Found realm: " + realm);
343 hasDomain = true;
344 }
345 }
346 return hasDomain;
347 }
348
349 /*
350 * UTILITIES
351 */
352 private LdapName serviceFactoryDn(String factoryPid) {
353 try {
354 return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
355 } catch (InvalidNameException e) {
356 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
357 }
358 }
359
360 private LdapName serviceDn(String servicePid) {
361 try {
362 return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
363 } catch (InvalidNameException e) {
364 throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
365 }
366 }
367
368 private LdapName serviceDn(String factoryPid, String cn) {
369 try {
370 return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
371 } catch (InvalidNameException e) {
372 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
373 }
374 }
375
376 public Dictionary<String, Object> getProps(String factoryPid, String cn) {
377 Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
378 if (attrs != null)
379 return new AttributesDictionary(attrs);
380 else
381 return null;
382 }
383
384 private static boolean isInitialized() {
385 return Files.exists(deployConfigPath);
386 }
387
388 public boolean isFirstInit() {
389 return isFirstInit;
390 }
391
392 }