View Javadoc
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 }