]> git.argeo.org Git - lgpl/argeo-commons.git/blob - DeployConfig.java
d52fe663bdea694c360ecadb622fa4d02401c09f
[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.Attributes;
16 import javax.naming.directory.BasicAttributes;
17 import javax.naming.ldap.LdapName;
18 import javax.naming.ldap.Rdn;
19 import javax.websocket.server.ServerEndpointConfig;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.argeo.cms.CmsException;
24 import org.argeo.cms.internal.http.InternalHttpConstants;
25 import org.argeo.cms.websocket.CmsWebSocketConfigurator;
26 import org.argeo.naming.AttributesDictionary;
27 import org.argeo.naming.LdifParser;
28 import org.argeo.naming.LdifWriter;
29 import org.argeo.node.NodeConstants;
30 import org.argeo.osgi.useradmin.UserAdminConf;
31 import org.eclipse.equinox.http.jetty.JettyConfigurator;
32 import org.osgi.framework.BundleContext;
33 import org.osgi.framework.FrameworkUtil;
34 import org.osgi.service.cm.Configuration;
35 import org.osgi.service.cm.ConfigurationAdmin;
36 import org.osgi.service.cm.ConfigurationEvent;
37 import org.osgi.service.cm.ConfigurationListener;
38
39 /** Manages the LDIF-based deployment configuration. */
40 class DeployConfig implements ConfigurationListener {
41 private final Log log = LogFactory.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 public DeployConfig(ConfigurationAdmin configurationAdmin, DataModels dataModels, boolean isClean) {
49 this.dataModels = dataModels;
50 // ConfigurationAdmin configurationAdmin =
51 // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
52 try {
53 boolean isFirstInit = false;
54 if (!isInitialized()) { // first init
55 isFirstInit = true;
56 firstInit();
57 }
58 init(configurationAdmin, isClean, isFirstInit);
59 } catch (IOException e) {
60 throw new CmsException("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.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 cn = UserAdminConf.baseDnHash(userDirectoryConfig);
106 activeCns.add(cn);
107 userDirectoryConfig.put(NodeConstants.CN, cn);
108 putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
109 }
110 // disable others
111 LdapName userAdminFactoryName = serviceFactoryDn(NodeConstants.NODE_USER_ADMIN_PID);
112 for (LdapName name : deployConfigs.keySet()) {
113 if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
114 try {
115 Attributes attrs = deployConfigs.get(name);
116 String cn = name.getRdn(name.size() - 1).getValue().toString();
117 if (!activeCns.contains(cn)) {
118 attrs.put(UserAdminConf.disabled.name(), "true");
119 }
120 } catch (Exception e) {
121 throw new CmsException("Cannot disable user directory " + name, e);
122 }
123 }
124 }
125 }
126
127 // http server
128 // Dictionary<String, Object> webServerConfig = InitUtils
129 // .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
130 // if (!webServerConfig.isEmpty()) {
131 // // TODO check for other customizers
132 // webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
133 // putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
134 // }
135 LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT);
136 if (deployConfigs.containsKey(defaultHttpServiceDn)) {
137 // remove old default configs since we have now to start Jetty servlet bridge
138 // indirectly
139 deployConfigs.remove(defaultHttpServiceDn);
140 }
141
142 // SAVE
143 save();
144 //
145
146 // Explicitly configures Jetty so that the default server is not started by the
147 // activator of the Equinox Jetty bundle.
148 Dictionary<String, Object> webServerConfig = InitUtils
149 .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
150 if (!webServerConfig.isEmpty()) {
151 webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
152
153 // TODO centralise with Jetty extender
154 Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
155 if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
156 bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
157 webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
158 }
159 }
160
161 int tryCount = 60;
162 try {
163 tryGettyJetty: while (tryCount > 0) {
164 try {
165 JettyConfigurator.startServer(KernelConstants.DEFAULT_JETTY_SERVER, webServerConfig);
166 // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
167 // configuration is not cleaned
168 FrameworkUtil.getBundle(JettyConfigurator.class).start();
169 break tryGettyJetty;
170 } catch (IllegalStateException e) {
171 // Jetty may not be ready
172 try {
173 Thread.sleep(1000);
174 } catch (Exception e1) {
175 // silent
176 }
177 }
178 }
179 } catch (Exception e) {
180 log.error("Cannot start default Jetty server with config " + webServerConfig, e);
181 }
182
183 }
184
185 private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException {
186
187 try (InputStream in = Files.newInputStream(deployConfigPath)) {
188 deployConfigs = new LdifParser().read(in);
189 }
190 if (isClean) {
191 setFromFrameworkProperties(isFirstInit);
192 for (LdapName dn : deployConfigs.keySet()) {
193 Rdn lastRdn = dn.getRdn(dn.size() - 1);
194 LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
195 if (prefix.toString().equals(NodeConstants.DEPLOY_BASEDN)) {
196 if (lastRdn.getType().equals(NodeConstants.CN)) {
197 // service
198 String pid = lastRdn.getValue().toString();
199 Configuration conf = configurationAdmin.getConfiguration(pid);
200 AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
201 conf.update(dico);
202 } else {
203 // service factory definition
204 }
205 } else {
206 // service factory service
207 Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
208 assert beforeLastRdn.getType().equals(NodeConstants.OU);
209 String factoryPid = beforeLastRdn.getValue().toString();
210 Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
211 AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
212 conf.update(dico);
213 }
214 }
215 }
216 // TODO check consistency if not clean
217 }
218
219 @Override
220 public void configurationEvent(ConfigurationEvent event) {
221 try {
222 if (ConfigurationEvent.CM_UPDATED == event.getType()) {
223 ConfigurationAdmin configurationAdmin = bc.getService(event.getReference());
224 Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
225 LdapName serviceDn = null;
226 String factoryPid = conf.getFactoryPid();
227 if (factoryPid != null) {
228 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
229 if (deployConfigs.containsKey(serviceFactoryDn)) {
230 for (LdapName dn : deployConfigs.keySet()) {
231 if (dn.startsWith(serviceFactoryDn)) {
232 Rdn lastRdn = dn.getRdn(dn.size() - 1);
233 assert lastRdn.getType().equals(NodeConstants.CN);
234 Object value = conf.getProperties().get(lastRdn.getType());
235 assert value != null;
236 if (value.equals(lastRdn.getValue())) {
237 serviceDn = dn;
238 break;
239 }
240 }
241 }
242
243 Object cn = conf.getProperties().get(NodeConstants.CN);
244 if (cn == null)
245 throw new IllegalArgumentException("Properties must contain cn");
246 if (serviceDn == null) {
247 putFactoryDeployConfig(factoryPid, conf.getProperties());
248 } else {
249 Attributes attrs = deployConfigs.get(serviceDn);
250 assert attrs != null;
251 AttributesDictionary.copy(conf.getProperties(), attrs);
252 }
253 save();
254 if (log.isDebugEnabled())
255 log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
256 } else {
257 // ignore non config-registered service factories
258 }
259 } else {
260 serviceDn = serviceDn(event.getPid());
261 if (deployConfigs.containsKey(serviceDn)) {
262 Attributes attrs = deployConfigs.get(serviceDn);
263 assert attrs != null;
264 AttributesDictionary.copy(conf.getProperties(), attrs);
265 save();
266 if (log.isDebugEnabled())
267 log.debug("Updated deploy config " + serviceDn);
268 } else {
269 // ignore non config-registered services
270 }
271 }
272 }
273 } catch (Exception e) {
274 log.error("Could not handle configuration event", e);
275 }
276 }
277
278 void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
279 Object cn = props.get(NodeConstants.CN);
280 if (cn == null)
281 throw new IllegalArgumentException("cn must be set in properties");
282 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
283 if (!deployConfigs.containsKey(serviceFactoryDn))
284 deployConfigs.put(serviceFactoryDn, new BasicAttributes(NodeConstants.OU, factoryPid));
285 LdapName serviceDn = serviceDn(factoryPid, cn.toString());
286 Attributes attrs = new BasicAttributes();
287 AttributesDictionary.copy(props, attrs);
288 deployConfigs.put(serviceDn, attrs);
289 }
290
291 void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
292 LdapName serviceDn = serviceDn(servicePid);
293 Attributes attrs = new BasicAttributes(NodeConstants.CN, servicePid);
294 AttributesDictionary.copy(props, attrs);
295 deployConfigs.put(serviceDn, attrs);
296 }
297
298 void save() {
299 try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
300 new LdifWriter(writer).write(deployConfigs);
301 } catch (IOException e) {
302 // throw new CmsException("Cannot save deploy configs", e);
303 log.error("Cannot save deploy configs", e);
304 }
305 }
306
307 boolean isStandalone(String dataModelName) {
308 return getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
309 }
310
311 /*
312 * UTILITIES
313 */
314 private LdapName serviceFactoryDn(String factoryPid) {
315 try {
316 return new LdapName(NodeConstants.OU + "=" + factoryPid + "," + NodeConstants.DEPLOY_BASEDN);
317 } catch (InvalidNameException e) {
318 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
319 }
320 }
321
322 private LdapName serviceDn(String servicePid) {
323 try {
324 return new LdapName(NodeConstants.CN + "=" + servicePid + "," + NodeConstants.DEPLOY_BASEDN);
325 } catch (InvalidNameException e) {
326 throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
327 }
328 }
329
330 private LdapName serviceDn(String factoryPid, String cn) {
331 try {
332 return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(NodeConstants.CN, cn));
333 } catch (InvalidNameException e) {
334 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
335 }
336 }
337
338 Dictionary<String, Object> getProps(String factoryPid, String cn) {
339 Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
340 if (attrs != null)
341 return new AttributesDictionary(attrs);
342 else
343 return null;
344 }
345
346 static boolean isInitialized() {
347 return Files.exists(deployConfigPath);
348 }
349
350 }