]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java
Merge branch 'master' of https://mbaudier@code.argeo.org/git/lgpl/argeo-commons.git
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / DeployConfig.java
1 package org.argeo.cms.internal.kernel;
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.List;
11 import java.util.SortedMap;
12 import java.util.TreeMap;
13
14 import javax.naming.InvalidNameException;
15 import javax.naming.directory.Attribute;
16 import javax.naming.directory.Attributes;
17 import javax.naming.directory.BasicAttributes;
18 import javax.naming.ldap.LdapName;
19 import javax.naming.ldap.Rdn;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.argeo.api.NodeConstants;
24 import org.argeo.naming.AttributesDictionary;
25 import org.argeo.naming.LdifParser;
26 import org.argeo.naming.LdifWriter;
27 import org.argeo.osgi.useradmin.UserAdminConf;
28 import org.eclipse.equinox.http.jetty.JettyConfigurator;
29 import org.osgi.framework.BundleContext;
30 import org.osgi.framework.FrameworkUtil;
31 import org.osgi.service.cm.Configuration;
32 import org.osgi.service.cm.ConfigurationAdmin;
33 import org.osgi.service.cm.ConfigurationEvent;
34 import org.osgi.service.cm.ConfigurationListener;
35
36 /** Manages the LDIF-based deployment configuration. */
37 class DeployConfig implements ConfigurationListener {
38 private final Log log = LogFactory.getLog(getClass());
39 private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
40
41 private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
42 private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
43 private final DataModels dataModels;
44
45 private boolean isFirstInit = false;
46
47 private final static String ROLES = "roles";
48
49 public DeployConfig(ConfigurationAdmin configurationAdmin, DataModels dataModels, boolean isClean) {
50 this.dataModels = dataModels;
51 // ConfigurationAdmin configurationAdmin =
52 // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
53 try {
54 if (!isInitialized()) { // first init
55 isFirstInit = true;
56 firstInit();
57 }
58 init(configurationAdmin, isClean, isFirstInit);
59 } catch (IOException e) {
60 throw new RuntimeException("Could not init deploy configs", e);
61 }
62 // FIXME check race conditions during initialization
63 // bc.registerService(ConfigurationListener.class, this, null);
64 }
65
66 private void firstInit() throws IOException {
67 log.info("## FIRST INIT ##");
68 Files.createDirectories(deployConfigPath.getParent());
69
70 // FirstInit firstInit = new FirstInit();
71 InitUtils.prepareFirstInitInstanceArea();
72
73 if (!Files.exists(deployConfigPath))
74 deployConfigs = new TreeMap<>();
75 else// config file could have juste been copied by preparation
76 try (InputStream in = Files.newInputStream(deployConfigPath)) {
77 deployConfigs = new LdifParser().read(in);
78 }
79 save();
80 }
81
82 private void setFromFrameworkProperties(boolean isFirstInit) {
83 // node repository
84 Dictionary<String, Object> nodeConfig = InitUtils
85 .getNodeRepositoryConfig(getProps(NodeConstants.NODE_REPOS_FACTORY_PID, NodeConstants.NODE));
86 // node repository is mandatory
87 putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, nodeConfig);
88
89 // additional repositories
90 dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) {
91 if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName()))
92 continue dataModels;
93 Dictionary<String, Object> config = InitUtils.getRepositoryConfig(dataModel.getName(),
94 getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName()));
95 if (config.size() != 0)
96 putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config);
97 }
98
99 // user admin
100 List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
101 if (userDirectoryConfigs.size() != 0) {
102 List<String> activeCns = new ArrayList<>();
103 for (int i = 0; i < userDirectoryConfigs.size(); i++) {
104 Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
105 String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
106 String cn;
107 if (NodeConstants.ROLES_BASEDN.equals(baseDn))
108 cn = ROLES;
109 else
110 cn = UserAdminConf.baseDnHash(userDirectoryConfig);
111 activeCns.add(cn);
112 userDirectoryConfig.put(NodeConstants.CN, cn);
113 putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
114 }
115 // disable others
116 LdapName userAdminFactoryName = serviceFactoryDn(NodeConstants.NODE_USER_ADMIN_PID);
117 for (LdapName name : deployConfigs.keySet()) {
118 if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
119 // try {
120 Attributes attrs = deployConfigs.get(name);
121 String cn = name.getRdn(name.size() - 1).getValue().toString();
122 if (!activeCns.contains(cn)) {
123 attrs.put(UserAdminConf.disabled.name(), "true");
124 }
125 // } catch (Exception e) {
126 // throw new CmsException("Cannot disable user directory " + name, e);
127 // }
128 }
129 }
130 }
131
132 // http server
133 // Dictionary<String, Object> webServerConfig = InitUtils
134 // .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
135 // if (!webServerConfig.isEmpty()) {
136 // // TODO check for other customizers
137 // webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
138 // putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
139 // }
140 LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT);
141 if (deployConfigs.containsKey(defaultHttpServiceDn)) {
142 // remove old default configs since we have now to start Jetty servlet bridge
143 // indirectly
144 deployConfigs.remove(defaultHttpServiceDn);
145 }
146
147 // SAVE
148 save();
149 //
150
151 // Explicitly configures Jetty so that the default server is not started by the
152 // activator of the Equinox Jetty bundle.
153 Dictionary<String, Object> webServerConfig = InitUtils
154 .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
155 // if (!webServerConfig.isEmpty()) {
156 // webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
157 //
158 // // TODO centralise with Jetty extender
159 // Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
160 // if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
161 // bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
162 // webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
163 // }
164 // }
165
166 int tryCount = 60;
167 try {
168 tryGettyJetty: while (tryCount > 0) {
169 try {
170 JettyConfigurator.startServer(KernelConstants.DEFAULT_JETTY_SERVER, webServerConfig);
171 // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
172 // configuration is not cleaned
173 FrameworkUtil.getBundle(JettyConfigurator.class).start();
174 break tryGettyJetty;
175 } catch (IllegalStateException e) {
176 // Jetty may not be ready
177 try {
178 Thread.sleep(1000);
179 } catch (Exception e1) {
180 // silent
181 }
182 tryCount--;
183 }
184 }
185 } catch (Exception e) {
186 log.error("Cannot start default Jetty server with config " + webServerConfig, e);
187 }
188
189 }
190
191 private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException {
192
193 try (InputStream in = Files.newInputStream(deployConfigPath)) {
194 deployConfigs = new LdifParser().read(in);
195 }
196 if (isClean) {
197 if (log.isDebugEnabled())
198 log.debug("Clean state, loading from framework properties...");
199 setFromFrameworkProperties(isFirstInit);
200
201 // FIXME make it more robust
202 Configuration systemRolesConf = null;
203 LdapName systemRolesDn;
204 try {
205 // FIXME make it more robust
206 systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
207 } catch (InvalidNameException e) {
208 throw new IllegalArgumentException(e);
209 }
210 deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
211 Rdn lastRdn = dn.getRdn(dn.size() - 1);
212 LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
213 if (prefix.toString().equals(NodeConstants.DEPLOY_BASEDN)) {
214 if (lastRdn.getType().equals(NodeConstants.CN)) {
215 // service
216 String pid = lastRdn.getValue().toString();
217 Configuration conf = configurationAdmin.getConfiguration(pid);
218 AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
219 conf.update(dico);
220 } else {
221 // service factory definition
222 }
223 } else {
224 Attributes config = deployConfigs.get(dn);
225 Attribute disabled = config.get(UserAdminConf.disabled.name());
226 if (disabled != null)
227 continue deployConfigs;
228 // service factory service
229 Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
230 assert beforeLastRdn.getType().equals(NodeConstants.OU);
231 String factoryPid = beforeLastRdn.getValue().toString();
232 Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
233 if (systemRolesDn.equals(dn)) {
234 systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
235 } else {
236 AttributesDictionary dico = new AttributesDictionary(config);
237 conf.update(dico);
238 }
239 }
240 }
241
242 // system roles must be last since it triggers node user admin publication
243 if (systemRolesConf == null)
244 throw new IllegalStateException("System roles are not configured.");
245 systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
246 }
247 // TODO check consistency if not clean
248 }
249
250 @Override
251 public void configurationEvent(ConfigurationEvent event) {
252 try {
253 if (ConfigurationEvent.CM_UPDATED == event.getType()) {
254 ConfigurationAdmin configurationAdmin = bc.getService(event.getReference());
255 Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
256 LdapName serviceDn = null;
257 String factoryPid = conf.getFactoryPid();
258 if (factoryPid != null) {
259 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
260 if (deployConfigs.containsKey(serviceFactoryDn)) {
261 for (LdapName dn : deployConfigs.keySet()) {
262 if (dn.startsWith(serviceFactoryDn)) {
263 Rdn lastRdn = dn.getRdn(dn.size() - 1);
264 assert lastRdn.getType().equals(NodeConstants.CN);
265 Object value = conf.getProperties().get(lastRdn.getType());
266 assert value != null;
267 if (value.equals(lastRdn.getValue())) {
268 serviceDn = dn;
269 break;
270 }
271 }
272 }
273
274 Object cn = conf.getProperties().get(NodeConstants.CN);
275 if (cn == null)
276 throw new IllegalArgumentException("Properties must contain cn");
277 if (serviceDn == null) {
278 putFactoryDeployConfig(factoryPid, conf.getProperties());
279 } else {
280 Attributes attrs = deployConfigs.get(serviceDn);
281 assert attrs != null;
282 AttributesDictionary.copy(conf.getProperties(), attrs);
283 }
284 save();
285 if (log.isDebugEnabled())
286 log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
287 } else {
288 // ignore non config-registered service factories
289 }
290 } else {
291 serviceDn = serviceDn(event.getPid());
292 if (deployConfigs.containsKey(serviceDn)) {
293 Attributes attrs = deployConfigs.get(serviceDn);
294 assert attrs != null;
295 AttributesDictionary.copy(conf.getProperties(), attrs);
296 save();
297 if (log.isDebugEnabled())
298 log.debug("Updated deploy config " + serviceDn);
299 } else {
300 // ignore non config-registered services
301 }
302 }
303 }
304 } catch (Exception e) {
305 log.error("Could not handle configuration event", e);
306 }
307 }
308
309 void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
310 Object cn = props.get(NodeConstants.CN);
311 if (cn == null)
312 throw new IllegalArgumentException("cn must be set in properties");
313 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
314 if (!deployConfigs.containsKey(serviceFactoryDn))
315 deployConfigs.put(serviceFactoryDn, new BasicAttributes(NodeConstants.OU, factoryPid));
316 LdapName serviceDn = serviceDn(factoryPid, cn.toString());
317 Attributes attrs = new BasicAttributes();
318 AttributesDictionary.copy(props, attrs);
319 deployConfigs.put(serviceDn, attrs);
320 }
321
322 void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
323 LdapName serviceDn = serviceDn(servicePid);
324 Attributes attrs = new BasicAttributes(NodeConstants.CN, servicePid);
325 AttributesDictionary.copy(props, attrs);
326 deployConfigs.put(serviceDn, attrs);
327 }
328
329 void save() {
330 try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
331 new LdifWriter(writer).write(deployConfigs);
332 } catch (IOException e) {
333 // throw new CmsException("Cannot save deploy configs", e);
334 log.error("Cannot save deploy configs", e);
335 }
336 }
337
338 boolean isStandalone(String dataModelName) {
339 return getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
340 }
341
342 /*
343 * UTILITIES
344 */
345 private LdapName serviceFactoryDn(String factoryPid) {
346 try {
347 return new LdapName(NodeConstants.OU + "=" + factoryPid + "," + NodeConstants.DEPLOY_BASEDN);
348 } catch (InvalidNameException e) {
349 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
350 }
351 }
352
353 private LdapName serviceDn(String servicePid) {
354 try {
355 return new LdapName(NodeConstants.CN + "=" + servicePid + "," + NodeConstants.DEPLOY_BASEDN);
356 } catch (InvalidNameException e) {
357 throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
358 }
359 }
360
361 private LdapName serviceDn(String factoryPid, String cn) {
362 try {
363 return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(NodeConstants.CN, cn));
364 } catch (InvalidNameException e) {
365 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
366 }
367 }
368
369 Dictionary<String, Object> getProps(String factoryPid, String cn) {
370 Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
371 if (attrs != null)
372 return new AttributesDictionary(attrs);
373 else
374 return null;
375 }
376
377 private static boolean isInitialized() {
378 return Files.exists(deployConfigPath);
379 }
380
381 public boolean isFirstInit() {
382 return isFirstInit;
383 }
384
385 }