View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.jcr.execution;
17  
18  import java.util.Arrays;
19  import java.util.Iterator;
20  import java.util.List;
21  
22  import javax.jcr.Node;
23  import javax.jcr.NodeIterator;
24  import javax.jcr.Property;
25  import javax.jcr.Repository;
26  import javax.jcr.RepositoryException;
27  import javax.jcr.Session;
28  import javax.jcr.nodetype.NodeType;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.argeo.jcr.JcrUtils;
33  import org.argeo.slc.SlcException;
34  import org.argeo.slc.SlcNames;
35  import org.argeo.slc.SlcTypes;
36  import org.argeo.slc.core.execution.PrimitiveSpecAttribute;
37  import org.argeo.slc.core.execution.PrimitiveValue;
38  import org.argeo.slc.core.execution.RefSpecAttribute;
39  import org.argeo.slc.core.execution.RefValueChoice;
40  import org.argeo.slc.deploy.ModuleDescriptor;
41  import org.argeo.slc.execution.ExecutionFlowDescriptor;
42  import org.argeo.slc.execution.ExecutionModuleDescriptor;
43  import org.argeo.slc.execution.ExecutionModulesListener;
44  import org.argeo.slc.execution.ExecutionModulesManager;
45  import org.argeo.slc.execution.ExecutionSpec;
46  import org.argeo.slc.execution.ExecutionSpecAttribute;
47  import org.argeo.slc.jcr.SlcJcrUtils;
48  
49  /**
50   * Synchronizes the local execution runtime with a JCR repository. For the time
51   * being the state is completely reset from one start to another.
52   */
53  public class JcrExecutionModulesListener implements ExecutionModulesListener,
54  		SlcNames {
55  	private final static String SLC_EXECUTION_MODULES_PROPERTY = "slc.executionModules";
56  
57  	private final static Log log = LogFactory
58  			.getLog(JcrExecutionModulesListener.class);
59  	private JcrAgent agent;
60  
61  	private ExecutionModulesManager modulesManager;
62  
63  	private Repository repository;
64  	/**
65  	 * We don't use a thread bound session because many different threads will
66  	 * call this critical component and we don't want to login each time. We
67  	 * therefore rather protect access to this session via synchronized.
68  	 */
69  	private Session session;
70  
71  	/*
72  	 * LIFECYCLE
73  	 */
74  	public void init() {
75  		try {
76  			session = repository.login();
77  			clearAgent();
78  			if (modulesManager != null) {
79  				Node agentNode = session.getNode(agent.getNodePath());
80  
81  				List<ModuleDescriptor> moduleDescriptors = modulesManager
82  						.listModules();
83  
84  				// scan SLC-ExecutionModule metadata
85  				for (ModuleDescriptor md : moduleDescriptors) {
86  					if (md.getMetadata().containsKey(
87  							ExecutionModuleDescriptor.SLC_EXECUTION_MODULE)) {
88  						String moduleNodeName = SlcJcrUtils
89  								.getModuleNodeName(md);
90  						Node moduleNode = agentNode.hasNode(moduleNodeName) ? agentNode
91  								.getNode(moduleNodeName) : agentNode
92  								.addNode(moduleNodeName);
93  						moduleNode.addMixin(SlcTypes.SLC_EXECUTION_MODULE);
94  						moduleNode.setProperty(SLC_NAME, md.getName());
95  						moduleNode.setProperty(SLC_VERSION, md.getVersion());
96  						moduleNode.setProperty(Property.JCR_TITLE,
97  								md.getTitle());
98  						moduleNode.setProperty(Property.JCR_DESCRIPTION,
99  								md.getDescription());
100 						moduleNode.setProperty(SLC_STARTED, md.getStarted());
101 					}
102 				}
103 
104 				// scan execution modules property
105 				String executionModules = System
106 						.getProperty(SLC_EXECUTION_MODULES_PROPERTY);
107 				if (executionModules != null) {
108 					for (String executionModule : executionModules.split(",")) {
109 						allModules: for (ModuleDescriptor md : moduleDescriptors) {
110 							String moduleNodeName = SlcJcrUtils
111 									.getModuleNodeName(md);
112 							if (md.getName().equals(executionModule)) {
113 								Node moduleNode = agentNode
114 										.hasNode(moduleNodeName) ? agentNode
115 										.getNode(moduleNodeName) : agentNode
116 										.addNode(moduleNodeName);
117 								moduleNode
118 										.addMixin(SlcTypes.SLC_EXECUTION_MODULE);
119 								moduleNode.setProperty(SLC_NAME, md.getName());
120 								moduleNode.setProperty(SLC_VERSION,
121 										md.getVersion());
122 								moduleNode.setProperty(Property.JCR_TITLE,
123 										md.getTitle());
124 								moduleNode.setProperty(
125 										Property.JCR_DESCRIPTION,
126 										md.getDescription());
127 								moduleNode.setProperty(SLC_STARTED,
128 										md.getStarted());
129 								break allModules;
130 							}
131 						}
132 					}
133 
134 					// save if needed
135 					if (session.hasPendingChanges())
136 						session.save();
137 				}
138 			}
139 		} catch (RepositoryException e) {
140 			JcrUtils.discardQuietly(session);
141 			JcrUtils.logoutQuietly(session);
142 			throw new SlcException("Cannot initialize modules", e);
143 		}
144 	}
145 
146 	public void destroy() {
147 		clearAgent();
148 		JcrUtils.logoutQuietly(session);
149 	}
150 
151 	protected synchronized void clearAgent() {
152 		try {
153 			Node agentNode = session.getNode(agent.getNodePath());
154 			for (NodeIterator nit = agentNode.getNodes(); nit.hasNext();)
155 				nit.nextNode().remove();
156 			session.save();
157 		} catch (RepositoryException e) {
158 			JcrUtils.discardQuietly(session);
159 			throw new SlcException("Cannot clear agent " + agent, e);
160 		}
161 	}
162 
163 	/*
164 	 * EXECUTION MODULES LISTENER
165 	 */
166 
167 	public synchronized void executionModuleAdded(
168 			ModuleDescriptor moduleDescriptor) {
169 		syncExecutionModule(moduleDescriptor);
170 	}
171 
172 	protected void syncExecutionModule(ModuleDescriptor moduleDescriptor) {
173 		try {
174 			Node agentNode = session.getNode(agent.getNodePath());
175 			String moduleNodeName = SlcJcrUtils
176 					.getModuleNodeName(moduleDescriptor);
177 			Node moduleNode = agentNode.hasNode(moduleNodeName) ? agentNode
178 					.getNode(moduleNodeName) : agentNode
179 					.addNode(moduleNodeName);
180 			moduleNode.addMixin(SlcTypes.SLC_EXECUTION_MODULE);
181 			moduleNode.setProperty(SLC_NAME, moduleDescriptor.getName());
182 			moduleNode.setProperty(SLC_VERSION, moduleDescriptor.getVersion());
183 			moduleNode.setProperty(Property.JCR_TITLE,
184 					moduleDescriptor.getTitle());
185 			moduleNode.setProperty(Property.JCR_DESCRIPTION,
186 					moduleDescriptor.getDescription());
187 			moduleNode.setProperty(SLC_STARTED, moduleDescriptor.getStarted());
188 			session.save();
189 		} catch (RepositoryException e) {
190 			JcrUtils.discardQuietly(session);
191 			throw new SlcException("Cannot sync module " + moduleDescriptor, e);
192 		}
193 	}
194 
195 	public synchronized void executionModuleRemoved(
196 			ModuleDescriptor moduleDescriptor) {
197 		try {
198 			String moduleName = SlcJcrUtils.getModuleNodeName(moduleDescriptor);
199 			Node agentNode = session.getNode(agent.getNodePath());
200 			if (agentNode.hasNode(moduleName)) {
201 				Node moduleNode = agentNode.getNode(moduleName);
202 				for (NodeIterator nit = moduleNode.getNodes(); nit.hasNext();) {
203 					nit.nextNode().remove();
204 				}
205 				moduleNode.setProperty(SLC_STARTED, false);
206 			}
207 			session.save();
208 		} catch (RepositoryException e) {
209 			JcrUtils.discardQuietly(session);
210 			throw new SlcException("Cannot remove module " + moduleDescriptor,
211 					e);
212 		}
213 	}
214 
215 	public synchronized void executionFlowAdded(ModuleDescriptor module,
216 			ExecutionFlowDescriptor efd) {
217 		try {
218 			Node agentNode = session.getNode(agent.getNodePath());
219 			Node moduleNode = agentNode.getNode(SlcJcrUtils
220 					.getModuleNodeName(module));
221 			String relativePath = getExecutionFlowRelativePath(efd);
222 			@SuppressWarnings("unused")
223 			Node flowNode = null;
224 			if (!moduleNode.hasNode(relativePath)) {
225 				flowNode = createExecutionFlowNode(moduleNode, relativePath,
226 						efd);
227 				session.save();
228 			} else {
229 				flowNode = moduleNode.getNode(relativePath);
230 			}
231 
232 			if (log.isTraceEnabled())
233 				log.trace("Flow " + efd + " added to JCR");
234 		} catch (RepositoryException e) {
235 			JcrUtils.discardQuietly(session);
236 			throw new SlcException("Cannot add flow " + efd + " from module "
237 					+ module, e);
238 		}
239 
240 	}
241 
242 	protected Node createExecutionFlowNode(Node moduleNode,
243 			String relativePath, ExecutionFlowDescriptor efd)
244 			throws RepositoryException {
245 		Node flowNode = null;
246 		List<String> pathTokens = Arrays.asList(relativePath.split("/"));
247 
248 		Iterator<String> names = pathTokens.iterator();
249 		// create intermediary paths
250 		Node currNode = moduleNode;
251 		while (names.hasNext()) {
252 			String name = names.next();
253 			if (currNode.hasNode(name))
254 				currNode = currNode.getNode(name);
255 			else {
256 				if (names.hasNext())
257 					currNode = currNode.addNode(name);
258 				else
259 					flowNode = currNode.addNode(name,
260 							SlcTypes.SLC_EXECUTION_FLOW);
261 			}
262 		}
263 
264 		// name, description
265 		flowNode.setProperty(SLC_NAME, efd.getName());
266 		String endName = pathTokens.get(pathTokens.size() - 1);
267 		flowNode.setProperty(Property.JCR_TITLE, endName);
268 		if (efd.getDescription() != null
269 				&& !efd.getDescription().trim().equals("")) {
270 			flowNode.setProperty(Property.JCR_DESCRIPTION, efd.getDescription());
271 		} else {
272 			flowNode.setProperty(Property.JCR_DESCRIPTION, endName);
273 		}
274 
275 		// execution spec
276 		ExecutionSpec executionSpec = efd.getExecutionSpec();
277 		String esName = executionSpec.getName();
278 		if (esName == null || esName.equals(ExecutionSpec.INTERNAL_NAME)
279 				|| esName.contains("#")/* automatically generated bean name */) {
280 			// internal spec node
281 			mapExecutionSpec(flowNode, executionSpec);
282 		} else {
283 			// reference spec node
284 			Node executionSpecsNode = moduleNode.hasNode(SLC_EXECUTION_SPECS) ? moduleNode
285 					.getNode(SLC_EXECUTION_SPECS) : moduleNode
286 					.addNode(SLC_EXECUTION_SPECS);
287 			Node executionSpecNode = executionSpecsNode.addNode(esName,
288 					SlcTypes.SLC_EXECUTION_SPEC);
289 			executionSpecNode.setProperty(SLC_NAME, esName);
290 			executionSpecNode.setProperty(Property.JCR_TITLE, esName);
291 			if (executionSpec.getDescription() != null
292 					&& !executionSpec.getDescription().trim().equals(""))
293 				executionSpecNode.setProperty(Property.JCR_DESCRIPTION,
294 						executionSpec.getDescription());
295 			mapExecutionSpec(executionSpecNode, executionSpec);
296 			flowNode.setProperty(SLC_SPEC, executionSpecNode);
297 		}
298 
299 		// flow values
300 		for (String attr : efd.getValues().keySet()) {
301 			ExecutionSpecAttribute esa = executionSpec.getAttributes()
302 					.get(attr);
303 			if (esa instanceof PrimitiveSpecAttribute) {
304 				PrimitiveSpecAttribute psa = (PrimitiveSpecAttribute) esa;
305 				// if spec reference there will be no node at this stage
306 				Node valueNode = JcrUtils.getOrAdd(flowNode, attr);
307 				valueNode.setProperty(SLC_TYPE, psa.getType());
308 				SlcJcrUtils.setPrimitiveAsProperty(valueNode, SLC_VALUE,
309 						(PrimitiveValue) efd.getValues().get(attr));
310 			}
311 		}
312 
313 		return flowNode;
314 	}
315 
316 	/**
317 	 * Base can be either an execution spec node, or an execution flow node (in
318 	 * case the execution spec is internal)
319 	 */
320 	protected void mapExecutionSpec(Node baseNode, ExecutionSpec executionSpec)
321 			throws RepositoryException {
322 		for (String attrName : executionSpec.getAttributes().keySet()) {
323 			ExecutionSpecAttribute esa = executionSpec.getAttributes().get(
324 					attrName);
325 			Node attrNode = baseNode.addNode(attrName);
326 			// booleans
327 			attrNode.addMixin(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE);
328 			attrNode.setProperty(SLC_IS_IMMUTABLE, esa.getIsImmutable());
329 			attrNode.setProperty(SLC_IS_CONSTANT, esa.getIsConstant());
330 			attrNode.setProperty(SLC_IS_HIDDEN, esa.getIsHidden());
331 
332 			if (esa instanceof PrimitiveSpecAttribute) {
333 				attrNode.addMixin(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE);
334 				PrimitiveSpecAttribute psa = (PrimitiveSpecAttribute) esa;
335 				SlcJcrUtils.setPrimitiveAsProperty(attrNode, SLC_VALUE, psa);
336 				attrNode.setProperty(SLC_TYPE, psa.getType());
337 			} else if (esa instanceof RefSpecAttribute) {
338 				attrNode.addMixin(SlcTypes.SLC_REF_SPEC_ATTRIBUTE);
339 				RefSpecAttribute rsa = (RefSpecAttribute) esa;
340 				attrNode.setProperty(SLC_TYPE, rsa.getTargetClassName());
341 				Object value = rsa.getValue();
342 				if (rsa.getChoices() != null) {
343 					Integer index = null;
344 					int count = 0;
345 					for (RefValueChoice choice : rsa.getChoices()) {
346 						String name = choice.getName();
347 						if (value != null && name.equals(value.toString()))
348 							index = count;
349 						Node choiceNode = attrNode.addNode(choice.getName());
350 						choiceNode.addMixin(NodeType.MIX_TITLE);
351 						choiceNode.setProperty(Property.JCR_TITLE,
352 								choice.getName());
353 						if (choice.getDescription() != null
354 								&& !choice.getDescription().trim().equals(""))
355 							choiceNode.setProperty(Property.JCR_DESCRIPTION,
356 									choice.getDescription());
357 						count++;
358 					}
359 
360 					if (index != null)
361 						attrNode.setProperty(SLC_VALUE, index);
362 				}
363 			}
364 		}
365 	}
366 
367 	public synchronized void executionFlowRemoved(ModuleDescriptor module,
368 			ExecutionFlowDescriptor executionFlow) {
369 		try {
370 			Node agentNode = session.getNode(agent.getNodePath());
371 			Node moduleNode = agentNode.getNode(SlcJcrUtils
372 					.getModuleNodeName(module));
373 			String relativePath = getExecutionFlowRelativePath(executionFlow);
374 			if (moduleNode.hasNode(relativePath))
375 				moduleNode.getNode(relativePath).remove();
376 			agentNode.getSession().save();
377 		} catch (RepositoryException e) {
378 			throw new SlcException("Cannot remove flow " + executionFlow
379 					+ " from module " + module, e);
380 		}
381 	}
382 
383 	/*
384 	 * UTILITIES
385 	 */
386 	/** @return the relative path, never starts with '/' */
387 	@SuppressWarnings("deprecation")
388 	protected String getExecutionFlowRelativePath(
389 			ExecutionFlowDescriptor executionFlow) {
390 		String relativePath = executionFlow.getPath() == null ? executionFlow
391 				.getName() : executionFlow.getPath() + '/'
392 				+ executionFlow.getName();
393 		// we assume that it is more than one char long
394 		if (relativePath.charAt(0) == '/')
395 			relativePath = relativePath.substring(1);
396 		// FIXME quick hack to avoid duplicate '/'
397 		relativePath = relativePath.replaceAll("//", "/");
398 		return relativePath;
399 	}
400 
401 	/*
402 	 * BEAN
403 	 */
404 	public void setAgent(JcrAgent agent) {
405 		this.agent = agent;
406 	}
407 
408 	public void setRepository(Repository repository) {
409 		this.repository = repository;
410 	}
411 
412 	public void setModulesManager(ExecutionModulesManager modulesManager) {
413 		this.modulesManager = modulesManager;
414 	}
415 
416 }