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.client.ui.editors;
17  
18  import java.util.ArrayList;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.SortedSet;
22  import java.util.TreeSet;
23  
24  import javax.jcr.Node;
25  import javax.jcr.NodeIterator;
26  import javax.jcr.Property;
27  import javax.jcr.RepositoryException;
28  import javax.jcr.nodetype.NodeType;
29  import javax.jcr.observation.Event;
30  import javax.jcr.observation.EventListener;
31  import javax.jcr.observation.ObservationManager;
32  import javax.jcr.query.Query;
33  import javax.jcr.query.QueryManager;
34  
35  import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
36  import org.argeo.jcr.JcrUtils;
37  import org.argeo.slc.SlcException;
38  import org.argeo.slc.SlcNames;
39  import org.argeo.slc.SlcTypes;
40  import org.argeo.slc.client.ui.SlcImages;
41  import org.argeo.slc.core.execution.PrimitiveAccessor;
42  import org.argeo.slc.core.execution.PrimitiveUtils;
43  import org.argeo.slc.execution.ExecutionProcess;
44  import org.argeo.slc.jcr.SlcJcrUtils;
45  import org.eclipse.jface.viewers.CellEditor;
46  import org.eclipse.jface.viewers.ColumnLabelProvider;
47  import org.eclipse.jface.viewers.ColumnViewer;
48  import org.eclipse.jface.viewers.ComboBoxCellEditor;
49  import org.eclipse.jface.viewers.EditingSupport;
50  import org.eclipse.jface.viewers.ISelectionChangedListener;
51  import org.eclipse.jface.viewers.IStructuredContentProvider;
52  import org.eclipse.jface.viewers.IStructuredSelection;
53  import org.eclipse.jface.viewers.ITreeContentProvider;
54  import org.eclipse.jface.viewers.SelectionChangedEvent;
55  import org.eclipse.jface.viewers.StructuredSelection;
56  import org.eclipse.jface.viewers.TableViewer;
57  import org.eclipse.jface.viewers.TableViewerColumn;
58  import org.eclipse.jface.viewers.TextCellEditor;
59  import org.eclipse.jface.viewers.TreeViewer;
60  import org.eclipse.jface.viewers.Viewer;
61  import org.eclipse.jface.viewers.ViewerDropAdapter;
62  import org.eclipse.swt.SWT;
63  import org.eclipse.swt.custom.SashForm;
64  import org.eclipse.swt.dnd.DND;
65  import org.eclipse.swt.dnd.TextTransfer;
66  import org.eclipse.swt.dnd.Transfer;
67  import org.eclipse.swt.dnd.TransferData;
68  import org.eclipse.swt.events.SelectionEvent;
69  import org.eclipse.swt.events.SelectionListener;
70  import org.eclipse.swt.graphics.Image;
71  import org.eclipse.swt.layout.FillLayout;
72  import org.eclipse.swt.layout.GridData;
73  import org.eclipse.swt.layout.GridLayout;
74  import org.eclipse.swt.layout.RowData;
75  import org.eclipse.swt.layout.RowLayout;
76  import org.eclipse.swt.widgets.Button;
77  import org.eclipse.swt.widgets.Composite;
78  import org.eclipse.swt.widgets.Label;
79  import org.eclipse.swt.widgets.Menu;
80  import org.eclipse.swt.widgets.MenuItem;
81  import org.eclipse.swt.widgets.Table;
82  import org.eclipse.ui.forms.AbstractFormPart;
83  import org.eclipse.ui.forms.IManagedForm;
84  import org.eclipse.ui.forms.editor.FormPage;
85  import org.eclipse.ui.forms.widgets.FormToolkit;
86  import org.eclipse.ui.forms.widgets.ScrolledForm;
87  
88  /** Definition of the process. */
89  public class ProcessBuilderPage extends FormPage implements SlcNames {
90  	// private final static Log log =
91  	// LogFactory.getLog(ProcessBuilderPage.class);
92  
93  	public final static String ID = "processBuilderPage";
94  
95  	/** To be displayed in empty lists */
96  	final static String NONE = "<none>";
97  
98  	private Node processNode;
99  
100 	private TreeViewer flowsViewer;
101 	private TableViewer valuesViewer;
102 	private Label statusLabel;
103 	private Button run;
104 	private Button remove;
105 	private Button clear;
106 
107 	private AbstractFormPart formPart;
108 	private EventListener statusObserver;
109 
110 	public ProcessBuilderPage(ProcessEditor editor, Node processNode) {
111 		super(editor, ID, "Definition");
112 		this.processNode = processNode;
113 	}
114 
115 	@Override
116 	protected void createFormContent(IManagedForm mf) {
117 		try {
118 			ScrolledForm form = mf.getForm();
119 			form.setExpandHorizontal(true);
120 			form.setExpandVertical(true);
121 			form.setText("Process " + processNode.getName());
122 			GridLayout mainLayout = new GridLayout(1, true);
123 			form.getBody().setLayout(mainLayout);
124 
125 			createControls(form.getBody());
126 			createBuilder(form.getBody());
127 
128 			// form
129 			formPart = new AbstractFormPart() {
130 
131 			};
132 			getManagedForm().addPart(formPart);
133 
134 			// observation
135 			statusObserver = new AsyncUiEventListener(form.getDisplay()) {
136 				protected void onEventInUiThread(List<Event> events) {
137 					statusChanged();
138 				}
139 			};
140 			ObservationManager observationManager = processNode.getSession()
141 					.getWorkspace().getObservationManager();
142 			observationManager.addEventListener(statusObserver,
143 					Event.PROPERTY_CHANGED, processNode.getPath(), true, null,
144 					null, false);
145 
146 			// make sure all controls are in line with status
147 			statusChanged();
148 
149 			// add initial flows
150 			addInitialFlows();
151 
152 		} catch (RepositoryException e) {
153 			throw new SlcException("Cannot create form content", e);
154 		}
155 	}
156 
157 	protected void createControls(Composite parent) {
158 		FormToolkit tk = getManagedForm().getToolkit();
159 
160 		Composite controls = tk.createComposite(parent);
161 		controls.setLayout(new RowLayout());
162 		controls.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
163 
164 		run = tk.createButton(controls, null, SWT.PUSH);
165 		run.setToolTipText("Run");
166 		run.setImage(SlcImages.LAUNCH);
167 		run.addSelectionListener(new SelectionListener() {
168 			public void widgetSelected(SelectionEvent e) {
169 				if (isFinished(getProcessStatus())) {
170 					((ProcessEditor) getEditor()).relaunch();
171 				} else if (isRunning(getProcessStatus())) {
172 					((ProcessEditor) getEditor()).kill();
173 				} else {
174 					((ProcessEditor) getEditor()).process();
175 				}
176 			}
177 
178 			public void widgetDefaultSelected(SelectionEvent e) {
179 				widgetSelected(e);
180 			}
181 		});
182 
183 		remove = tk.createButton(controls, null, SWT.PUSH);
184 		remove.setImage(SlcImages.REMOVE_ONE);
185 		remove.setToolTipText("Remove selected flows");
186 		remove.addSelectionListener(new SelectionListener() {
187 			public void widgetSelected(SelectionEvent e) {
188 				removeSelectedFlows();
189 			}
190 
191 			public void widgetDefaultSelected(SelectionEvent e) {
192 				widgetSelected(e);
193 			}
194 		});
195 
196 		clear = tk.createButton(controls, null, SWT.PUSH);
197 		clear.setImage(SlcImages.REMOVE_ALL);
198 		clear.setToolTipText("Clear all flows");
199 		clear.addSelectionListener(new SelectionListener() {
200 			public void widgetSelected(SelectionEvent e) {
201 				removeAllFlows();
202 			}
203 
204 			public void widgetDefaultSelected(SelectionEvent e) {
205 				widgetSelected(e);
206 			}
207 		});
208 
209 		Composite statusComposite = tk.createComposite(controls);
210 		RowData rowData = new RowData();
211 		rowData.width = 100;
212 		rowData.height = 16;
213 		statusComposite.setLayoutData(rowData);
214 		statusComposite.setLayout(new FillLayout());
215 		statusLabel = tk.createLabel(statusComposite, getProcessStatus());
216 
217 	}
218 
219 	protected void createBuilder(Composite parent) {
220 		FormToolkit tk = getManagedForm().getToolkit();
221 		SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL);
222 		sashForm.setSashWidth(4);
223 		GridData sahFormGd = new GridData(SWT.FILL, SWT.FILL, true, true);
224 		sahFormGd.widthHint = 400;
225 		sashForm.setLayoutData(sahFormGd);
226 
227 		Composite flowsComposite = tk.createComposite(sashForm);
228 		flowsComposite.setLayout(new GridLayout(1, false));
229 
230 		flowsViewer = new TreeViewer(flowsComposite);
231 		flowsViewer.getTree().setLayoutData(
232 				new GridData(SWT.FILL, SWT.FILL, true, true));
233 		flowsViewer.setLabelProvider(new FlowsLabelProvider());
234 		flowsViewer.setContentProvider(new FlowsContentProvider());
235 		flowsViewer.addSelectionChangedListener(new FlowsSelectionListener());
236 
237 		int operations = DND.DROP_COPY | DND.DROP_MOVE;
238 		Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
239 		flowsViewer.addDropSupport(operations, tt, new FlowsDropListener(
240 				flowsViewer));
241 
242 		// Context menu
243 		addContextMenu();
244 
245 		flowsViewer.setInput(getEditorSite());
246 		flowsViewer.setInput(processNode);
247 
248 		Composite valuesComposite = tk.createComposite(sashForm);
249 		valuesComposite.setLayout(new GridLayout(1, false));
250 
251 		valuesViewer = new TableViewer(valuesComposite);
252 		GridData valuedGd = new GridData(SWT.FILL, SWT.FILL, true, true);
253 		// valuedGd.widthHint = 200;
254 		valuesViewer.getTable().setLayoutData(valuedGd);
255 		valuesViewer.getTable().setHeaderVisible(true);
256 
257 		valuesViewer.setContentProvider(new ValuesContentProvider());
258 		initializeValuesViewer(valuesViewer);
259 		sashForm.setWeights(getWeights());
260 		valuesViewer.setInput(getEditorSite());
261 	}
262 
263 	/** Creates the columns of the values viewer */
264 	protected void initializeValuesViewer(TableViewer viewer) {
265 		String[] titles = { "Name", "Value" };
266 		int[] bounds = { 200, 100 };
267 
268 		for (int i = 0; i < titles.length; i++) {
269 			TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
270 			column.getColumn().setText(titles[i]);
271 			column.getColumn().setWidth(bounds[i]);
272 			column.getColumn().setResizable(true);
273 			column.getColumn().setMoveable(true);
274 			if (i == 0) {
275 				column.setLabelProvider(new ColumnLabelProvider() {
276 					public String getText(Object element) {
277 						try {
278 							Node specAttrNode = (Node) element;
279 							return specAttrNode.getName();
280 						} catch (RepositoryException e) {
281 							throw new SlcException("Cannot get value", e);
282 						}
283 					}
284 				});
285 			} else if (i == 1) {
286 				column.setLabelProvider(new ColumnLabelProvider() {
287 					public String getText(Object element) {
288 						return getAttributeSpecText((Node) element);
289 					}
290 				});
291 				column.setEditingSupport(new ValuesEditingSupport(viewer));
292 			}
293 
294 		}
295 		Table table = viewer.getTable();
296 		table.setHeaderVisible(false);
297 		table.setLinesVisible(true);
298 	}
299 
300 	protected int[] getWeights() {
301 		return new int[] { 50, 50 };
302 	}
303 
304 	/*
305 	 * CONTROLLERS
306 	 */
307 	/** Reflects a status change */
308 	protected void statusChanged() {
309 		String status = getProcessStatus();
310 		statusLabel.setText(status);
311 		Boolean isEditable = isEditable(status);
312 		run.setEnabled(status.equals(ExecutionProcess.RUNNING) || isEditable);
313 		remove.setEnabled(isEditable);
314 		clear.setEnabled(isEditable);
315 		// flowsViewer.getTree().setEnabled(isEditable);
316 		if (status.equals(ExecutionProcess.RUNNING)) {
317 			run.setEnabled(true);
318 			run.setImage(SlcImages.KILL);
319 			run.setToolTipText("Kill");
320 		} else if (isFinished(status)) {
321 			run.setEnabled(true);
322 			run.setImage(SlcImages.RELAUNCH);
323 			run.setToolTipText("Relaunch");
324 		}
325 
326 		if (flowsViewer != null)
327 			flowsViewer.refresh();
328 	}
329 
330 	/** Adds initial flows from the editor input if any */
331 	protected void addInitialFlows() {
332 		for (String path : ((ProcessEditorInput) getEditorInput())
333 				.getInitialFlowPaths()) {
334 			addFlow(path);
335 		}
336 	}
337 
338 	/**
339 	 * Adds a new flow.
340 	 * 
341 	 * @param path
342 	 *            the path of the flow
343 	 */
344 	protected void addFlow(String path) {
345 		try {
346 			Node flowNode = processNode.getSession().getNode(path);
347 			Node realizedFlowNode = processNode.getNode(SLC_FLOW).addNode(
348 					SLC_FLOW);
349 			realizedFlowNode.setProperty(SLC_NAME,
350 					flowNode.getProperty(SLC_NAME).getString());
351 			realizedFlowNode.addMixin(SlcTypes.SLC_REALIZED_FLOW);
352 			Node address = realizedFlowNode.addNode(SLC_ADDRESS,
353 					NodeType.NT_ADDRESS);
354 			address.setProperty(Property.JCR_PATH, path);
355 
356 			// copy spec attributes
357 			Node specAttrsBase;
358 			if (flowNode.hasProperty(SLC_SPEC)) {
359 				Node executionSpecNode = flowNode.getProperty(SLC_SPEC)
360 						.getNode();
361 				specAttrsBase = executionSpecNode;
362 				String executionSpecName = executionSpecNode.getProperty(
363 						SLC_NAME).getString();
364 				realizedFlowNode.setProperty(SLC_SPEC, executionSpecName);
365 			} else
366 				specAttrsBase = flowNode;
367 
368 			specAttrs: for (NodeIterator nit = specAttrsBase.getNodes(); nit
369 					.hasNext();) {
370 				Node specAttrNode = nit.nextNode();
371 				String attrName = specAttrNode.getName();
372 				if (!specAttrNode
373 						.isNodeType(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE))
374 					continue specAttrs;
375 				Node realizedAttrNode = realizedFlowNode.addNode(specAttrNode
376 						.getName());
377 				JcrUtils.copy(specAttrNode, realizedAttrNode);
378 
379 				// override with flow value
380 				if (flowNode.hasNode(attrName)) {
381 					// assuming this is a primitive
382 					Node attrNode = flowNode.getNode(attrName);
383 					if (attrNode.hasProperty(SLC_VALUE))
384 						realizedAttrNode.setProperty(SLC_VALUE, attrNode
385 								.getProperty(SLC_VALUE).getValue());
386 				}
387 			}
388 
389 			// Part title
390 			StringBuilder editorTitle = new StringBuilder();
391 			NodeIterator it = realizedFlowNode.getParent().getNodes(SLC_FLOW);
392 			while (it.hasNext()) {
393 				Node rFlowNode = it.nextNode();
394 				String name = rFlowNode.getProperty(SLC_NAME).getString();
395 				editorTitle.append(name).append(' ');
396 			}
397 			((ProcessEditor) getEditor())
398 					.setEditorTitle(editorTitle.toString());
399 
400 			flowsViewer.refresh();
401 			formPart.markDirty();
402 		} catch (RepositoryException e) {
403 			throw new SlcException("Cannot add flow " + path, e);
404 		}
405 	}
406 
407 	@SuppressWarnings("unchecked")
408 	protected void removeSelectedFlows() {
409 		if (!flowsViewer.getSelection().isEmpty()) {
410 			Iterator<Object> it = ((StructuredSelection) flowsViewer
411 					.getSelection()).iterator();
412 			while (it.hasNext()) {
413 				Node node = (Node) it.next();
414 				try {
415 					node.remove();
416 				} catch (RepositoryException e) {
417 					throw new SlcException("Cannot remove " + node, e);
418 				}
419 			}
420 			flowsViewer.refresh();
421 			formPart.markDirty();
422 		}
423 	}
424 
425 	protected void removeAllFlows() {
426 		try {
427 			for (NodeIterator nit = processNode.getNode(SLC_FLOW).getNodes(); nit
428 					.hasNext();) {
429 				nit.nextNode().remove();
430 			}
431 			flowsViewer.refresh();
432 			formPart.markDirty();
433 		} catch (RepositoryException e) {
434 			throw new SlcException("Cannot remove flows from " + processNode,
435 					e);
436 		}
437 	}
438 
439 	public void commit(Boolean onSave) {
440 		if (onSave)
441 			statusLabel.setText(getProcessStatus());
442 		formPart.commit(onSave);
443 	}
444 
445 	/*
446 	 * STATE
447 	 */
448 	protected String getProcessStatus() {
449 		try {
450 			return processNode.getProperty(SLC_STATUS).getString();
451 		} catch (RepositoryException e) {
452 			throw new SlcException("Cannot retrieve status for " + processNode,
453 					e);
454 		}
455 	}
456 
457 	/** Optimization so that we don't call the node each time */
458 	protected static Boolean isEditable(String status) {
459 		return status.equals(ExecutionProcess.NEW)
460 				|| status.equals(ExecutionProcess.INITIALIZED);
461 	}
462 
463 	protected static Boolean isFinished(String status) {
464 		return status.equals(ExecutionProcess.COMPLETED)
465 				|| status.equals(ExecutionProcess.ERROR)
466 				|| status.equals(ExecutionProcess.KILLED);
467 	}
468 
469 	protected static Boolean isRunning(String status) {
470 		return status.equals(ExecutionProcess.RUNNING);
471 	}
472 
473 	/*
474 	 * LIFECYCLE
475 	 */
476 	@Override
477 	public void dispose() {
478 		JcrUtils.unregisterQuietly(processNode, statusObserver);
479 		super.dispose();
480 	}
481 
482 	/*
483 	 * UTILITIES
484 	 */
485 	protected static String getAttributeSpecText(Node specAttrNode) {
486 		try {
487 			if (specAttrNode.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) {
488 				if (!specAttrNode.hasProperty(SLC_VALUE))
489 					return "";
490 				String type = specAttrNode.getProperty(SLC_TYPE).getString();
491 				if (PrimitiveAccessor.TYPE_PASSWORD.equals(type))
492 					return "****************";
493 				Object value = PrimitiveUtils.convert(type, specAttrNode
494 						.getProperty(SLC_VALUE).getString());
495 				return value.toString();
496 			} else if (specAttrNode.isNodeType(SlcTypes.SLC_REF_SPEC_ATTRIBUTE)) {
497 				if (specAttrNode.hasProperty(SLC_VALUE)) {
498 					int value = (int) specAttrNode.getProperty(SLC_VALUE)
499 							.getLong();
500 					NodeIterator children = specAttrNode.getNodes();
501 					int index = 0;
502 					while (children.hasNext()) {
503 						Node child = children.nextNode();
504 						if (index == value)
505 							return child.getProperty(Property.JCR_TITLE)
506 									.getString();
507 						index++;
508 					}
509 					throw new SlcException("No child node with index " + value
510 							+ " for spec attribute " + specAttrNode);
511 				} else
512 					return "";
513 			}
514 			throw new SlcException("Unsupported type for spec attribute "
515 					+ specAttrNode);
516 		} catch (RepositoryException e) {
517 			throw new SlcException("Cannot get value", e);
518 		}
519 	}
520 
521 	/*
522 	 * FLOWS SUBCLASSES
523 	 */
524 	class FlowsContentProvider implements ITreeContentProvider {
525 		public Object[] getElements(Object obj) {
526 			if (!(obj instanceof Node))
527 				return new Object[0];
528 
529 			try {
530 				Node node = (Node) obj;
531 				List<Node> children = new ArrayList<Node>();
532 				for (NodeIterator nit = node.getNode(SLC_FLOW).getNodes(); nit
533 						.hasNext();) {
534 					Node flowNode = nit.nextNode();
535 					children.add(flowNode);
536 				}
537 				return children.toArray();
538 			} catch (RepositoryException e) {
539 				throw new SlcException("Cannot list flows of " + obj, e);
540 			}
541 		}
542 
543 		public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
544 		}
545 
546 		public void dispose() {
547 		}
548 
549 		public Object[] getChildren(Object parentElement) {
550 			// no children for the time being
551 			return null;
552 		}
553 
554 		public Object getParent(Object element) {
555 			return null;
556 		}
557 
558 		public boolean hasChildren(Object element) {
559 			return false;
560 		}
561 
562 	}
563 
564 	static class FlowsLabelProvider extends ColumnLabelProvider {
565 		@Override
566 		public String getText(Object element) {
567 			Node node = (Node) element;
568 			try {
569 				if (node.isNodeType(SlcTypes.SLC_REALIZED_FLOW)) {
570 					if (node.hasNode(SLC_ADDRESS)) {
571 						String path = node.getNode(SLC_ADDRESS)
572 								.getProperty(Property.JCR_PATH).getString();
573 						String executionModuleName = SlcJcrUtils
574 								.moduleName(path);
575 						// Node executionModuleNode = node.getSession().getNode(
576 						// SlcJcrUtils.modulePath(path));
577 						// String executionModuleName = executionModuleNode
578 						// .getProperty(SLC_NAME).getString();
579 						return executionModuleName + ":"
580 								+ SlcJcrUtils.flowRelativePath(path);
581 					}
582 				}
583 			} catch (RepositoryException e) {
584 				throw new SlcException("Cannot display " + element, e);
585 			}
586 			return super.getText(element);
587 		}
588 
589 		@Override
590 		public Image getImage(Object element) {
591 			Node node = (Node) element;
592 			try {
593 				if (node.isNodeType(SlcTypes.SLC_REALIZED_FLOW)) {
594 					if (node.hasProperty(SLC_STATUS)) {
595 						String status = node.getProperty(SLC_STATUS)
596 								.getString();
597 						// TODO: factorize with process view ?
598 						if (status.equals(ExecutionProcess.RUNNING))
599 							return SlcImages.PROCESS_RUNNING;
600 						else if (status.equals(ExecutionProcess.ERROR)
601 								|| status.equals(ExecutionProcess.KILLED))
602 							return SlcImages.PROCESS_ERROR;
603 						else if (status.equals(ExecutionProcess.COMPLETED))
604 							return SlcImages.PROCESS_COMPLETED;
605 					}
606 					return SlcImages.FLOW;
607 				}
608 			} catch (RepositoryException e) {
609 				throw new SlcException("Cannot display " + element, e);
610 			}
611 			return super.getImage(element);
612 		}
613 
614 	}
615 
616 	/** Parameter view is updated each time a new line is selected */
617 	class FlowsSelectionListener implements ISelectionChangedListener {
618 		public void selectionChanged(SelectionChangedEvent evt) {
619 			if (evt.getSelection().isEmpty()) {
620 				valuesViewer.setInput(getEditorSite());
621 				return;
622 			}
623 			Node realizedFlowNode = (Node) ((IStructuredSelection) evt
624 					.getSelection()).getFirstElement();
625 			valuesViewer.setInput(realizedFlowNode);
626 		}
627 	}
628 
629 	/**
630 	 * Add a context menu that call private methods. It only relies on selected
631 	 * item(s) not on parameter that are passed in the menuAboutToShow method
632 	 **/
633 	private void addContextMenu() {
634 		Menu menu = new Menu(flowsViewer.getControl());
635 
636 		MenuItem removeItems = new MenuItem(menu, SWT.PUSH);
637 		removeItems.addSelectionListener(new SelectionListener() {
638 
639 			public void widgetSelected(SelectionEvent e) {
640 				removeSelectedFlows();
641 			}
642 
643 			public void widgetDefaultSelected(SelectionEvent e) {
644 			}
645 		});
646 		removeItems.setText("Remove selected flow(s)");
647 
648 		MenuItem removeAllItems = new MenuItem(menu, SWT.PUSH);
649 		removeAllItems.addSelectionListener(new SelectionListener() {
650 
651 			public void widgetSelected(SelectionEvent e) {
652 				removeAllFlows();
653 			}
654 
655 			public void widgetDefaultSelected(SelectionEvent e) {
656 			}
657 		});
658 		removeAllItems.setText("Remove all flows");
659 		flowsViewer.getTree().setMenu(menu);
660 	}
661 
662 	/** Manages drop event. */
663 	class FlowsDropListener extends ViewerDropAdapter {
664 
665 		public FlowsDropListener(Viewer viewer) {
666 			super(viewer);
667 		}
668 
669 		@Override
670 		public boolean performDrop(Object data) {
671 
672 			// Parse the received String, paths are separated with a carriage
673 			// return
674 			String[] paths = data.toString().split(new String("\n"));
675 			SortedSet<String> resultPaths = new TreeSet<String>();
676 			for (String path : paths) {
677 				try {
678 					// either a node or a whole directory can have been dragged
679 					QueryManager qm = processNode.getSession().getWorkspace()
680 							.getQueryManager();
681 					String statement = "SELECT * FROM ["
682 							+ SlcTypes.SLC_EXECUTION_FLOW
683 							+ "] WHERE ISDESCENDANTNODE(['" + path
684 							+ "']) OR ISSAMENODE(['" + path + "'])";
685 					Query query = qm.createQuery(statement, Query.JCR_SQL2);
686 
687 					// order paths
688 					for (NodeIterator nit = query.execute().getNodes(); nit
689 							.hasNext();) {
690 						String currPath = nit.nextNode().getPath();
691 						// do not add twice a same flow
692 						if (!resultPaths.contains(currPath))
693 							resultPaths.add(currPath);
694 					}
695 				} catch (RepositoryException e) {
696 					throw new SlcException("Cannot query flows under " + path,
697 							e);
698 				}
699 			}
700 			for (String p : resultPaths) {
701 				addFlow(p);
702 			}
703 			return true;
704 
705 		}
706 
707 		@Override
708 		public boolean validateDrop(Object target, int operation,
709 				TransferData transferType) {
710 			return isEditable(getProcessStatus());
711 		}
712 	}
713 
714 	/*
715 	 * VALUES SUBCLASSES
716 	 */
717 	static class ValuesContentProvider implements IStructuredContentProvider {
718 
719 		public Object[] getElements(Object inputElement) {
720 			if (!(inputElement instanceof Node))
721 				return new Object[0];
722 
723 			try {
724 				Node realizedFlowNode = (Node) inputElement;
725 				List<Node> specAttributes = new ArrayList<Node>();
726 				specAttrs: for (NodeIterator nit = realizedFlowNode.getNodes(); nit
727 						.hasNext();) {
728 					Node specAttrNode = nit.nextNode();
729 					if (!specAttrNode
730 							.isNodeType(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE))
731 						continue specAttrs;
732 					// workaround to enable hiding of necessary but unusable
733 					// flow parameters
734 					else if (specAttrNode.hasProperty(SlcNames.SLC_IS_HIDDEN)
735 							&& specAttrNode.getProperty(SlcNames.SLC_IS_HIDDEN)
736 									.getBoolean())
737 						continue specAttrs;
738 					specAttributes.add(specAttrNode);
739 				}
740 				return specAttributes.toArray();
741 			} catch (RepositoryException e) {
742 				throw new SlcException("Cannot get elements", e);
743 			}
744 		}
745 
746 		public void dispose() {
747 		}
748 
749 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
750 		}
751 	}
752 
753 	class ValuesEditingSupport extends EditingSupport {
754 		private final TableViewer tableViewer;
755 
756 		public ValuesEditingSupport(ColumnViewer viewer) {
757 			super(viewer);
758 			tableViewer = (TableViewer) viewer;
759 		}
760 
761 		@Override
762 		protected CellEditor getCellEditor(Object element) {
763 			try {
764 				Node specAttrNode = (Node) element;
765 				if (specAttrNode
766 						.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) {
767 					String type = specAttrNode.getProperty(SLC_TYPE)
768 							.getString();
769 					if (PrimitiveAccessor.TYPE_PASSWORD.equals(type)) {
770 						return new TextCellEditor(tableViewer.getTable(),
771 								SWT.PASSWORD);
772 					} else {
773 						return new TextCellEditor(tableViewer.getTable());
774 					}
775 				} else if (specAttrNode
776 						.isNodeType(SlcTypes.SLC_REF_SPEC_ATTRIBUTE)) {
777 					NodeIterator children = specAttrNode.getNodes();
778 					ArrayList<String> items = new ArrayList<String>();
779 					while (children.hasNext()) {
780 						Node child = children.nextNode();
781 						if (child.isNodeType(NodeType.MIX_TITLE))
782 							items.add(child.getProperty(Property.JCR_TITLE)
783 									.getString());
784 					}
785 					return new ComboBoxCellEditor(tableViewer.getTable(),
786 							items.toArray(new String[items.size()]));
787 				}
788 				return null;
789 			} catch (RepositoryException e) {
790 				throw new SlcException("Cannot get cell editor", e);
791 			}
792 		}
793 
794 		@Override
795 		protected boolean canEdit(Object element) {
796 			try {
797 				Node specAttrNode = (Node) element;
798 				Boolean cannotEdit = specAttrNode.getProperty(SLC_IS_IMMUTABLE)
799 						.getBoolean()
800 						|| specAttrNode.getProperty(SLC_IS_CONSTANT)
801 								.getBoolean();
802 				return !cannotEdit && isSupportedAttributeType(specAttrNode);
803 			} catch (RepositoryException e) {
804 				throw new SlcException("Cannot check whether " + element
805 						+ " is editable", e);
806 			}
807 		}
808 
809 		/**
810 		 * Supports {@link SlcTypes#SLC_PRIMITIVE_SPEC_ATTRIBUTE} and
811 		 * {@link SlcTypes#SLC_REF_SPEC_ATTRIBUTE}
812 		 */
813 		protected boolean isSupportedAttributeType(Node specAttrNode)
814 				throws RepositoryException {
815 			return specAttrNode
816 					.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)
817 					|| specAttrNode.isNodeType(SlcTypes.SLC_REF_SPEC_ATTRIBUTE);
818 		}
819 
820 		@Override
821 		protected Object getValue(Object element) {
822 			Node specAttrNode = (Node) element;
823 			try {
824 				if (specAttrNode
825 						.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) {
826 					if (!specAttrNode.hasProperty(SLC_VALUE))
827 						return NONE;
828 					String type = specAttrNode.getProperty(SLC_TYPE)
829 							.getString();
830 					// TODO optimize based on data type?
831 					Object value = PrimitiveUtils.convert(type, specAttrNode
832 							.getProperty(SLC_VALUE).getString());
833 					return value.toString();
834 				} else if (specAttrNode
835 						.isNodeType(SlcTypes.SLC_REF_SPEC_ATTRIBUTE)) {
836 					if (!specAttrNode.hasProperty(SLC_VALUE))
837 						return 0;
838 					// return the index of the sub node as set by setValue()
839 					// in the future we may manage references as well
840 					return (int) specAttrNode.getProperty(SLC_VALUE).getLong();
841 				}
842 				throw new SlcException("Unsupported type for spec attribute "
843 						+ specAttrNode);
844 			} catch (RepositoryException e) {
845 				throw new SlcException("Cannot get value for " + element, e);
846 			}
847 		}
848 
849 		@Override
850 		protected void setValue(Object element, Object value) {
851 			try {
852 				Node specAttrNode = (Node) element;
853 				if (specAttrNode
854 						.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) {
855 					String type = specAttrNode.getProperty(SLC_TYPE)
856 							.getString();
857 					SlcJcrUtils.setPrimitiveAsProperty(specAttrNode, SLC_VALUE,
858 							type, value);
859 					valuesViewer.refresh();
860 					formPart.markDirty();
861 				} else if (specAttrNode
862 						.isNodeType(SlcTypes.SLC_REF_SPEC_ATTRIBUTE)) {
863 					specAttrNode.setProperty(SLC_VALUE,
864 							((Integer) value).longValue());
865 					valuesViewer.refresh();
866 					formPart.markDirty();
867 				}
868 			} catch (RepositoryException e) {
869 				throw new SlcException("Cannot get celle editor", e);
870 			}
871 		}
872 
873 	}
874 }