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