]> git.argeo.org Git - lgpl/argeo-commons.git/blob - internal/kernel/DeployConfig.java
Prepare next development cycle
[lgpl/argeo-commons.git] / 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.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.api.NodeConstants;
24 import org.argeo.cms.CmsException;
25 import org.argeo.cms.internal.http.InternalHttpConstants;
26 import org.argeo.cms.websocket.CmsWebSocketConfigurator;
27 import org.argeo.naming.AttributesDictionary;
28 import org.argeo.naming.LdifParser;
29 import org.argeo.naming.LdifWriter;
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_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 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 tryCount--;
178 }
179 }
180 } catch (Exception e) {
181 log.error("Cannot start default Jetty server with config " + webServerConfig, e);
182 }
183
184 }
185
186 private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException {
187
188 try (InputStream in = Files.newInputStream(deployConfigPath)) {
189 deployConfigs = new LdifParser().read(in);
190 }
191 if (isClean) {
192 if(log.isDebugEnabled())
193 log.debug("Clean state, loading from framework properties...");
194 setFromFrameworkProperties(isFirstInit);
195 for (LdapName dn : deployConfigs.keySet()) {
196 Rdn lastRdn = dn.getRdn(dn.size() - 1);
197 LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
198 if (prefix.toString().equals(NodeConstants.DEPLOY_BASEDN)) {
199 if (lastRdn.getType().equals(NodeConstants.CN)) {
200 // service
201 String pid = lastRdn.getValue().toString();
202 Configuration conf = configurationAdmin.getConfiguration(pid);
203 AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
204 conf.update(dico);
205 } else {
206 // service factory definition
207 }
208 } else {
209 // service factory service
210 Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
211 assert beforeLastRdn.getType().equals(NodeConstants.OU);
212 String factoryPid = beforeLastRdn.getValue().toString();
213 Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
214 AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
215 conf.update(dico);
216 }
217 }
218 }
219 // TODO check consistency if not clean
220 }
221
222 @Override
223 public void configurationEvent(ConfigurationEvent event) {
224 try {
225 if (ConfigurationEvent.CM_UPDATED == event.getType()) {
226 ConfigurationAdmin configurationAdmin = bc.getService(event.getReference());
227 Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
228 LdapName serviceDn = null;
229 String factoryPid = conf.getFactoryPid();
230 if (factoryPid != null) {
231 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
232 if (deployConfigs.containsKey(serviceFactoryDn)) {
233 for (LdapName dn : deployConfigs.keySet()) {
234 if (dn.startsWith(serviceFactoryDn)) {
235 Rdn lastRdn = dn.getRdn(dn.size() - 1);
236 assert lastRdn.getType().equals(NodeConstants.CN);
237 Object value = conf.getProperties().get(lastRdn.getType());
238 assert value != null;
239 if (value.equals(lastRdn.getValue())) {
240 serviceDn = dn;
241 break;
242 }
243 }
244 }
245
246 Object cn = conf.getProperties().get(NodeConstants.CN);
247 if (cn == null)
248 throw new IllegalArgumentException("Properties must contain cn");
249 if (serviceDn == null) {
250 putFactoryDeployConfig(factoryPid, conf.getProperties());
251 } else {
252 Attributes attrs = deployConfigs.get(serviceDn);
253 assert attrs != null;
254 AttributesDictionary.copy(conf.getProperties(), attrs);
255 }
256 save();
257 if (log.isDebugEnabled())
258 log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
259 } else {
260 // ignore non config-registered service factories
261 }
262 } else {
263 serviceDn = serviceDn(event.getPid());
264 if (deployConfigs.containsKey(serviceDn)) {
265 Attributes attrs = deployConfigs.get(serviceDn);
266 assert attrs != null;
267 AttributesDictionary.copy(conf.getProperties(), attrs);
268 save();
269 if (log.isDebugEnabled())
270 log.debug("Updated deploy config " + serviceDn);
271 } else {
272 // ignore non config-registered services
273 }
274 }
275 }
276 } catch (Exception e) {
277 log.error("Could not handle configuration event", e);
278 }
279 }
280
281 void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
282 Object cn = props.get(NodeConstants.CN);
283 if (cn == null)
284 throw new IllegalArgumentException("cn must be set in properties");
285 LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
286 if (!deployConfigs.containsKey(serviceFactoryDn))
287 deployConfigs.put(serviceFactoryDn, new BasicAttributes(NodeConstants.OU, factoryPid));
288 LdapName serviceDn = serviceDn(factoryPid, cn.toString());
289 Attributes attrs = new BasicAttributes();
290 AttributesDictionary.copy(props, attrs);
291 deployConfigs.put(serviceDn, attrs);
292 }
293
294 void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
295 LdapName serviceDn = serviceDn(servicePid);
296 Attributes attrs = new BasicAttributes(NodeConstants.CN, servicePid);
297 AttributesDictionary.copy(props, attrs);
298 deployConfigs.put(serviceDn, attrs);
299 }
300
301 void save() {
302 try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
303 new LdifWriter(writer).write(deployConfigs);
304 } catch (IOException e) {
305 // throw new CmsException("Cannot save deploy configs", e);
306 log.error("Cannot save deploy configs", e);
307 }
308 }
309
310 boolean isStandalone(String dataModelName) {
311 return getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
312 }
313
314 /*
315 * UTILITIES
316 */
317 private LdapName serviceFactoryDn(String factoryPid) {
318 try {
319 return new LdapName(NodeConstants.OU + "=" + factoryPid + "," + NodeConstants.DEPLOY_BASEDN);
320 } catch (InvalidNameException e) {
321 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
322 }
323 }
324
325 private LdapName serviceDn(String servicePid) {
326 try {
327 return new LdapName(NodeConstants.CN + "=" + servicePid + "," + NodeConstants.DEPLOY_BASEDN);
328 } catch (InvalidNameException e) {
329 throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
330 }
331 }
332
333 private LdapName serviceDn(String factoryPid, String cn) {
334 try {
335 return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(NodeConstants.CN, cn));
336 } catch (InvalidNameException e) {
337 throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
338 }
339 }
340
341 Dictionary<String, Object> getProps(String factoryPid, String cn) {
342 Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
343 if (attrs != null)
344 return new AttributesDictionary(attrs);
345 else
346 return null;
347 }
348
349 static boolean isInitialized() {
350 return Files.exists(deployConfigPath);
351 }
352
353 }