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.views;
17  
18  import java.text.DateFormat;
19  import java.text.SimpleDateFormat;
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.Iterator;
23  import java.util.List;
24  
25  import javax.jcr.Node;
26  import javax.jcr.NodeIterator;
27  import javax.jcr.Property;
28  import javax.jcr.Repository;
29  import javax.jcr.RepositoryException;
30  import javax.jcr.Session;
31  import javax.jcr.nodetype.NodeType;
32  import javax.jcr.observation.Event;
33  import javax.jcr.observation.EventListener;
34  import javax.jcr.observation.ObservationManager;
35  
36  import org.argeo.cms.ui.workbench.util.CommandUtils;
37  import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
38  import org.argeo.jcr.JcrUtils;
39  import org.argeo.slc.SlcException;
40  import org.argeo.slc.SlcNames;
41  import org.argeo.slc.SlcTypes;
42  import org.argeo.slc.client.ui.ClientUiPlugin;
43  import org.argeo.slc.client.ui.SlcUiConstants;
44  import org.argeo.slc.client.ui.commands.AddResultFolder;
45  import org.argeo.slc.client.ui.commands.DeleteItems;
46  import org.argeo.slc.client.ui.commands.RefreshJcrResultTreeView;
47  import org.argeo.slc.client.ui.commands.RenameResultFolder;
48  import org.argeo.slc.client.ui.commands.RenameResultNode;
49  import org.argeo.slc.client.ui.editors.ProcessEditor;
50  import org.argeo.slc.client.ui.editors.ProcessEditorInput;
51  import org.argeo.slc.client.ui.model.ParentNodeFolder;
52  import org.argeo.slc.client.ui.model.ResultFolder;
53  import org.argeo.slc.client.ui.model.ResultItemsComparator;
54  import org.argeo.slc.client.ui.model.ResultItemsComparer;
55  import org.argeo.slc.client.ui.model.ResultParent;
56  import org.argeo.slc.client.ui.model.ResultParentUtils;
57  import org.argeo.slc.client.ui.model.SingleResultNode;
58  import org.argeo.slc.client.ui.model.VirtualFolder;
59  import org.argeo.slc.client.ui.providers.ResultTreeContentProvider;
60  import org.argeo.slc.client.ui.providers.ResultTreeLabelProvider;
61  import org.argeo.slc.jcr.SlcJcrResultUtils;
62  import org.eclipse.jface.action.IMenuListener;
63  import org.eclipse.jface.action.IMenuManager;
64  import org.eclipse.jface.action.MenuManager;
65  import org.eclipse.jface.dialogs.MessageDialog;
66  import org.eclipse.jface.viewers.ColumnLabelProvider;
67  import org.eclipse.jface.viewers.DecoratingLabelProvider;
68  import org.eclipse.jface.viewers.DoubleClickEvent;
69  import org.eclipse.jface.viewers.IDoubleClickListener;
70  import org.eclipse.jface.viewers.ILabelDecorator;
71  import org.eclipse.jface.viewers.ISelectionChangedListener;
72  import org.eclipse.jface.viewers.IStructuredContentProvider;
73  import org.eclipse.jface.viewers.IStructuredSelection;
74  import org.eclipse.jface.viewers.SelectionChangedEvent;
75  import org.eclipse.jface.viewers.TableViewer;
76  import org.eclipse.jface.viewers.TableViewerColumn;
77  import org.eclipse.jface.viewers.TreePath;
78  import org.eclipse.jface.viewers.TreeViewer;
79  import org.eclipse.jface.viewers.Viewer;
80  import org.eclipse.jface.viewers.ViewerDropAdapter;
81  import org.eclipse.swt.SWT;
82  import org.eclipse.swt.custom.SashForm;
83  import org.eclipse.swt.dnd.DND;
84  import org.eclipse.swt.dnd.DragSourceEvent;
85  import org.eclipse.swt.dnd.DragSourceListener;
86  import org.eclipse.swt.dnd.TextTransfer;
87  import org.eclipse.swt.dnd.Transfer;
88  import org.eclipse.swt.dnd.TransferData;
89  import org.eclipse.swt.layout.FillLayout;
90  import org.eclipse.swt.layout.GridData;
91  import org.eclipse.swt.layout.GridLayout;
92  import org.eclipse.swt.widgets.Composite;
93  import org.eclipse.swt.widgets.Display;
94  import org.eclipse.swt.widgets.Menu;
95  import org.eclipse.ui.ISharedImages;
96  import org.eclipse.ui.IWorkbenchPage;
97  import org.eclipse.ui.IWorkbenchWindow;
98  import org.eclipse.ui.PlatformUI;
99  import org.eclipse.ui.part.ViewPart;
100 
101 /** SLC generic JCR Result tree view. */
102 public class JcrResultTreeView extends ViewPart {
103 	public final static String ID = ClientUiPlugin.ID + ".jcrResultTreeView";
104 
105 	private final static DateFormat dateFormat = new SimpleDateFormat(
106 			SlcUiConstants.DEFAULT_DISPLAY_DATE_TIME_FORMAT);
107 
108 	// private final static Log log =
109 	// LogFactory.getLog(JcrResultTreeView.class);
110 
111 	/* DEPENDENCY INJECTION */
112 	private Repository repository;
113 	private Session session;
114 
115 	// This page widgets
116 	private TreeViewer resultTreeViewer;
117 	private TableViewer propertiesViewer;
118 
119 	private EventListener myResultsObserver = null;
120 	private EventListener allResultsObserver = null;
121 
122 	// under My Results
123 	private final static String[] observedNodeTypesUnderMyResult = {
124 			SlcTypes.SLC_TEST_RESULT, SlcTypes.SLC_RESULT_FOLDER,
125 			SlcTypes.SLC_MY_RESULT_ROOT_FOLDER };
126 
127 	private final static String[] observedNodeTypesUnderAllResults = {
128 			SlcTypes.SLC_TEST_RESULT, NodeType.NT_UNSTRUCTURED };
129 
130 	private boolean isResultFolder = false;
131 
132 	/**
133 	 * To be overridden to adapt size of form and result frames.
134 	 */
135 	protected int[] getWeights() {
136 		return new int[] { 70, 30 };
137 	}
138 
139 	@Override
140 	public void createPartControl(Composite parent) {
141 		try {
142 			session = repository.login();
143 		} catch (RepositoryException e1) {
144 			throw new SlcException("Cannot log in to repository");
145 		}
146 
147 		parent.setLayout(new FillLayout());
148 		// Main layout
149 		SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
150 		sashForm.setSashWidth(4);
151 		sashForm.setLayout(new FillLayout());
152 
153 		// Create the tree on top of the view
154 		Composite top = new Composite(sashForm, SWT.NONE);
155 		GridLayout gl = new GridLayout(1, false);
156 		top.setLayout(gl);
157 		resultTreeViewer = createResultsTreeViewer(top);
158 
159 		// Create the property viewer on the bottom
160 		Composite bottom = new Composite(sashForm, SWT.NONE);
161 		bottom.setLayout(new GridLayout(1, false));
162 		propertiesViewer = createPropertiesViewer(bottom);
163 
164 		sashForm.setWeights(getWeights());
165 
166 		setOrderedInput(resultTreeViewer);
167 
168 		// Initialize observer
169 		try {
170 			ObservationManager observationManager = session.getWorkspace()
171 					.getObservationManager();
172 			myResultsObserver = new MyResultsObserver(resultTreeViewer
173 					.getTree().getDisplay());
174 			allResultsObserver = new AllResultsObserver(resultTreeViewer
175 					.getTree().getDisplay());
176 
177 			// observe tree changes under MyResults
178 			observationManager.addEventListener(myResultsObserver,
179 					Event.NODE_ADDED | Event.NODE_REMOVED,
180 					SlcJcrResultUtils.getMyResultsBasePath(session), true,
181 					null, observedNodeTypesUnderMyResult, false);
182 			// observe tree changes under All results
183 			observationManager.addEventListener(allResultsObserver,
184 					Event.NODE_ADDED | Event.NODE_REMOVED,
185 					SlcJcrResultUtils.getSlcResultsBasePath(session), true,
186 					null, observedNodeTypesUnderAllResults, false);
187 		} catch (RepositoryException e) {
188 			throw new SlcException("Cannot register listeners", e);
189 		}
190 	}
191 
192 	/**
193 	 * Override default behaviour so that default defined order remains
194 	 * unchanged on first level of the tree
195 	 */
196 	private void setOrderedInput(TreeViewer viewer) {
197 		// Add specific ordering
198 		viewer.setInput(null);
199 		viewer.setComparator(null);
200 		viewer.setInput(initializeResultTree());
201 		viewer.setComparator(new ResultItemsComparator());
202 	}
203 
204 	// The main tree viewer
205 	protected TreeViewer createResultsTreeViewer(Composite parent) {
206 		int style = SWT.BORDER | SWT.MULTI;
207 
208 		TreeViewer viewer = new TreeViewer(parent, style);
209 		viewer.getTree().setLayoutData(
210 				new GridData(SWT.FILL, SWT.FILL, true, true));
211 
212 		viewer.setContentProvider(new ResultTreeContentProvider());
213 
214 		// Add label provider with label decorator
215 		ResultTreeLabelProvider rtLblProvider = new ResultTreeLabelProvider();
216 		ILabelDecorator decorator = ClientUiPlugin.getDefault().getWorkbench()
217 				.getDecoratorManager().getLabelDecorator();
218 		viewer.setLabelProvider(new DecoratingLabelProvider(rtLblProvider,
219 				decorator));
220 		viewer.addDoubleClickListener(new ViewDoubleClickListener());
221 
222 		// Override default behaviour to insure that 2 distincts results that
223 		// have the same name will be correctly and distincly returned by
224 		// corresponding TreeViewer.getSelection() method.
225 		viewer.setComparer(new ResultItemsComparer());
226 
227 		// viewer.setLabelProvider(rtLblProvider);
228 		getSite().setSelectionProvider(viewer);
229 
230 		// add drag & drop support
231 		int operations = DND.DROP_COPY | DND.DROP_MOVE;
232 		Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
233 		viewer.addDragSupport(operations, tt, new ViewDragListener());
234 		viewer.addDropSupport(operations, tt, new ViewDropListener(viewer));
235 
236 		// add context menu
237 		MenuManager menuManager = new MenuManager();
238 		Menu menu = menuManager.createContextMenu(viewer.getTree());
239 		menuManager.addMenuListener(new IMenuListener() {
240 			public void menuAboutToShow(IMenuManager manager) {
241 				contextMenuAboutToShow(manager);
242 			}
243 		});
244 		viewer.getTree().setMenu(menu);
245 		menuManager.setRemoveAllWhenShown(true);
246 
247 		getSite().registerContextMenu(menuManager, viewer);
248 
249 		// add change listener to display TestResult information in the property
250 		// viewer
251 		viewer.addSelectionChangedListener(new MySelectionChangedListener());
252 		return viewer;
253 	}
254 
255 	// Detailed property viewer
256 	protected TableViewer createPropertiesViewer(Composite parent) {
257 		propertiesViewer = new TableViewer(parent);
258 		propertiesViewer.getTable().setLayoutData(
259 				new GridData(SWT.FILL, SWT.FILL, true, true));
260 		propertiesViewer.getTable().setHeaderVisible(true);
261 		propertiesViewer.setContentProvider(new PropertiesContentProvider());
262 		TableViewerColumn col = new TableViewerColumn(propertiesViewer,
263 				SWT.NONE);
264 		col.getColumn().setText("Name");
265 		col.getColumn().setWidth(100);
266 		col.setLabelProvider(new ColumnLabelProvider() {
267 			public String getText(Object element) {
268 				try {
269 					String name = ((Property) element).getName();
270 					String value = null;
271 					if (SlcNames.SLC_TEST_CASE.equals(name))
272 						value = "Test case";
273 					else if (SlcNames.SLC_COMPLETED.equals(name))
274 						value = "Completed on";
275 					else if (SlcNames.SLC_SUCCESS.equals(name))
276 						value = "Status";
277 					else if (SlcNames.SLC_MESSAGE.equals(name))
278 						value = "Message";
279 					else if (SlcNames.SLC_ERROR_MESSAGE.equals(name))
280 						value = "Error";
281 					return value;
282 				} catch (RepositoryException e) {
283 					throw new SlcException(
284 							"Unexpected exception in label provider", e);
285 				}
286 			}
287 		});
288 		col = new TableViewerColumn(propertiesViewer, SWT.NONE);
289 		col.getColumn().setText("Value");
290 		col.getColumn().setWidth(200);
291 		col.setLabelProvider(new ColumnLabelProvider() {
292 			public String getText(Object element) {
293 				try {
294 					Property property = (Property) element;
295 					String name = property.getName();
296 					String value = null;
297 
298 					if (SlcNames.SLC_TEST_CASE.equals(name)
299 							|| SlcNames.SLC_ERROR_MESSAGE.equals(name)
300 							|| SlcNames.SLC_MESSAGE.equals(name))
301 						value = property.getValue().getString();
302 					else if (SlcNames.SLC_COMPLETED.equals(name)) {
303 						Calendar date = property.getValue().getDate();
304 						value = dateFormat.format(date.getTime());
305 					} else if (SlcNames.SLC_SUCCESS.equals(name)) {
306 						if (property.getValue().getBoolean())
307 							value = "PASSED";
308 						else {
309 							if (property.getParent().hasProperty(
310 									SlcNames.SLC_ERROR_MESSAGE))
311 								value = "ERROR";
312 							else
313 								value = "FAILED";
314 						}
315 					}
316 					return value;
317 				} catch (RepositoryException e) {
318 					throw new SlcException(
319 							"Unexpected exception in label provider", e);
320 				}
321 			}
322 		});
323 		propertiesViewer.setInput(getViewSite());
324 		return propertiesViewer;
325 	}
326 
327 	/**
328 	 * Override to provide specific behaviour. Typically to enable the display
329 	 * of a result file.
330 	 * 
331 	 * @param evt
332 	 */
333 	protected void processDoubleClick(DoubleClickEvent evt) {
334 		Object obj = ((IStructuredSelection) evt.getSelection())
335 				.getFirstElement();
336 		try {
337 			if (obj instanceof SingleResultNode) {
338 				SingleResultNode srNode = (SingleResultNode) obj;
339 				Node node = srNode.getNode();
340 				// FIXME: open a default result editor
341 				if (node.isNodeType(SlcTypes.SLC_PROCESS)) {
342 					IWorkbenchPage activePage = PlatformUI.getWorkbench()
343 							.getActiveWorkbenchWindow().getActivePage();
344 					activePage.openEditor(
345 							new ProcessEditorInput(node.getPath()),
346 							ProcessEditor.ID);
347 				}
348 			}
349 		} catch (Exception e) {
350 			throw new SlcException("Cannot open " + obj, e);
351 		}
352 	}
353 
354 	@Override
355 	public void setFocus() {
356 	}
357 
358 	/**
359 	 * refreshes the passed resultParent and its corresponding subtree. It
360 	 * refreshes the whole viewer if null is passed.
361 	 * 
362 	 * @param ResultParent
363 	 * 
364 	 */
365 	public void refresh(ResultParent resultParent) {
366 		if (resultParent == null) {
367 			if (!resultTreeViewer.getTree().isDisposed()) {
368 				TreePath[] tps = resultTreeViewer.getExpandedTreePaths();
369 				setOrderedInput(resultTreeViewer);
370 				resultTreeViewer.setExpandedTreePaths(tps);
371 			} else
372 				setOrderedInput(resultTreeViewer);
373 		} else {
374 			if (resultParent instanceof ParentNodeFolder) {
375 				ParentNodeFolder currFolder = (ParentNodeFolder) resultParent;
376 				jcrRefresh(currFolder.getNode());
377 				currFolder.forceFullRefresh();
378 			}
379 			// FIXME: specific refresh does not work
380 			// resultTreeViewer.refresh(resultParent, true);
381 			refresh(null);
382 		}
383 	}
384 
385 	/**
386 	 * refreshes the passed node and its corresponding subtree.
387 	 * 
388 	 * @param node
389 	 *            cannot be null
390 	 * 
391 	 */
392 	public boolean jcrRefresh(Node node) {
393 		// if (log.isDebugEnabled())
394 		// log.debug(" JCR refreshing " + node + "...");
395 		// Thread.dumpStack();
396 		boolean isPassed = true;
397 		try {
398 			if (node.isNodeType(SlcTypes.SLC_TEST_RESULT)) {
399 				isPassed = node.getNode(SlcNames.SLC_AGGREGATED_STATUS)
400 						.getProperty(SlcNames.SLC_SUCCESS).getBoolean();
401 			} else if (node.isNodeType(SlcTypes.SLC_RESULT_FOLDER)) {
402 				NodeIterator ni = node.getNodes();
403 				while (ni.hasNext()) {
404 					Node currChild = ni.nextNode();
405 					isPassed = isPassed & jcrRefresh(currChild);
406 				}
407 				if (isPassed != node.getNode(SlcNames.SLC_AGGREGATED_STATUS)
408 						.getProperty(SlcNames.SLC_SUCCESS).getBoolean()) {
409 					node.getNode(SlcNames.SLC_AGGREGATED_STATUS).setProperty(
410 							SlcNames.SLC_SUCCESS, isPassed);
411 					node.getSession().save();
412 					return isPassed;
413 				}
414 			} else
415 				; // do nothing
416 		} catch (RepositoryException e) {
417 			throw new SlcException("Cannot register listeners", e);
418 		}
419 		return isPassed;
420 	}
421 
422 	private ResultParent[] initializeResultTree() {
423 		try {
424 			// Force initialization of the tree structure if needed
425 			SlcJcrResultUtils.getSlcResultsParentNode(session);
426 			SlcJcrResultUtils.getMyResultParentNode(session);
427 			// Remove yesterday and last 7 days virtual folders
428 			// ResultParent[] roots = new ResultParent[5];
429 			ResultParent[] roots = new ResultParent[3];
430 
431 			// My results
432 			roots[0] = new ParentNodeFolder(null,
433 					SlcJcrResultUtils.getMyResultParentNode(session),
434 					SlcUiConstants.DEFAULT_MY_RESULTS_FOLDER_LABEL);
435 
436 			// today
437 			Calendar cal = Calendar.getInstance();
438 			String relPath = JcrUtils.dateAsPath(cal);
439 			List<String> datePathes = new ArrayList<String>();
440 			datePathes.add(relPath);
441 			roots[1] = new VirtualFolder(null,
442 					ResultParentUtils.getResultsForDates(session, datePathes),
443 					"Today");
444 
445 			// // Yesterday
446 			// cal = Calendar.getInstance();
447 			// cal.add(Calendar.DAY_OF_YEAR, -1);
448 			// relPath = JcrUtils.dateAsPath(cal);
449 			// datePathes = new ArrayList<String>();
450 			// datePathes.add(relPath);
451 			// roots[2] = new VirtualFolder(null,
452 			// ResultParentUtils.getResultsForDates(session, datePathes),
453 			// "Yesterday");
454 			// // Last 7 days
455 			//
456 			// cal = Calendar.getInstance();
457 			// datePathes = new ArrayList<String>();
458 			//
459 			// for (int i = 0; i < 7; i++) {
460 			// cal.add(Calendar.DAY_OF_YEAR, -i);
461 			// relPath = JcrUtils.dateAsPath(cal);
462 			// datePathes.add(relPath);
463 			// }
464 			// roots[3] = new VirtualFolder(null,
465 			// ResultParentUtils.getResultsForDates(session, datePathes),
466 			// "Last 7 days");
467 
468 			// All results
469 			Node otherResultsPar = session.getNode(SlcJcrResultUtils
470 					.getSlcResultsBasePath(session));
471 			// roots[4] = new ParentNodeFolder(null, otherResultsPar,
472 			// "All results");
473 			roots[2] = new ParentNodeFolder(null, otherResultsPar,
474 					"All results");
475 			return roots;
476 		} catch (RepositoryException re) {
477 			throw new SlcException(
478 					"Unexpected error while initializing ResultTree.", re);
479 		}
480 	}
481 
482 	// Manage context menu
483 	/**
484 	 * Defines the commands that will pop up in the context menu.
485 	 **/
486 	protected void contextMenuAboutToShow(IMenuManager menuManager) {
487 		IWorkbenchWindow window = ClientUiPlugin.getDefault().getWorkbench()
488 				.getActiveWorkbenchWindow();
489 
490 		IStructuredSelection selection = (IStructuredSelection) resultTreeViewer
491 				.getSelection();
492 		boolean canAddSubfolder = false;
493 		boolean canRenamefolder = false;
494 		boolean isSingleResultNode = false;
495 		boolean isUnderMyResult = false;
496 		boolean validMultipleDelete = false;
497 		try {
498 
499 			// Building conditions
500 			if (selection.size() == 1) {
501 				Object obj = selection.getFirstElement();
502 				if (obj instanceof SingleResultNode)
503 					isSingleResultNode = true;
504 				else if (obj instanceof ParentNodeFolder) {
505 					Node cNode = ((ParentNodeFolder) obj).getNode();
506 					if (cNode.isNodeType(SlcTypes.SLC_RESULT_FOLDER)) {
507 						canAddSubfolder = true;
508 						canRenamefolder = true;
509 						isUnderMyResult = true;
510 					} else if (cNode
511 							.isNodeType(SlcTypes.SLC_MY_RESULT_ROOT_FOLDER)) {
512 						canAddSubfolder = true;
513 					}
514 				}
515 			} else {
516 				@SuppressWarnings("rawtypes")
517 				Iterator it = selection.iterator();
518 				multicheck: while (it.hasNext()) {
519 					validMultipleDelete = true;
520 					Object obj = it.next();
521 					if (obj instanceof SingleResultNode)
522 						continue multicheck;
523 					else if (obj instanceof ParentNodeFolder) {
524 						Node cNode = ((ParentNodeFolder) obj).getNode();
525 						if (cNode.isNodeType(SlcTypes.SLC_RESULT_FOLDER))
526 							continue multicheck;
527 						else {
528 							validMultipleDelete = false;
529 							break multicheck;
530 						}
531 					} else {
532 						validMultipleDelete = false;
533 						break multicheck;
534 					}
535 				}
536 			}
537 		} catch (RepositoryException re) {
538 			throw new SlcException(
539 					"unexpected error while building condition for context menu",
540 					re);
541 		}
542 
543 		// Effective Refresh
544 		CommandUtils.refreshCommand(menuManager, window,
545 				RefreshJcrResultTreeView.ID,
546 				RefreshJcrResultTreeView.DEFAULT_LABEL,
547 				RefreshJcrResultTreeView.DEFAULT_IMG_DESCRIPTOR, true);
548 
549 		CommandUtils.refreshCommand(menuManager, window, DeleteItems.ID,
550 				DeleteItems.DEFAULT_LABEL, DeleteItems.DEFAULT_IMG_DESCRIPTOR,
551 				isUnderMyResult || isSingleResultNode || validMultipleDelete);
552 
553 		CommandUtils.refreshCommand(menuManager, window, AddResultFolder.ID,
554 				AddResultFolder.DEFAULT_LABEL,
555 				ClientUiPlugin.getDefault().getWorkbench().getSharedImages()
556 						.getImageDescriptor(ISharedImages.IMG_OBJ_ADD),
557 				canAddSubfolder);
558 
559 		CommandUtils.refreshCommand(menuManager, window, RenameResultFolder.ID,
560 				RenameResultFolder.DEFAULT_LABEL,
561 				RenameResultFolder.DEFAULT_IMG_DESCRIPTOR, canRenamefolder);
562 
563 		// Command removed for the time being.
564 		CommandUtils.refreshCommand(menuManager, window, RenameResultNode.ID,
565 				RenameResultNode.DEFAULT_LABEL,
566 				RenameResultNode.DEFAULT_IMG_DESCRIPTOR, false);
567 
568 		// Test to be removed
569 		// If you use this pattern, do not forget to call
570 		// menuManager.setRemoveAllWhenShown(true);
571 		// when creating the menuManager
572 
573 		// menuManager.add(new Action("Test") {
574 		// public void run() {
575 		// log.debug("do something");
576 		// }
577 		// });
578 	}
579 
580 	/* INNER CLASSES */
581 	class ViewDragListener implements DragSourceListener {
582 
583 		public void dragStart(DragSourceEvent event) {
584 			// Check if the drag action should start.
585 			IStructuredSelection selection = (IStructuredSelection) resultTreeViewer
586 					.getSelection();
587 			boolean doIt = false;
588 
589 			// FIXME clean this code.
590 			try {
591 				if (selection.size() == 1) {
592 					Object obj = selection.getFirstElement();
593 					if (obj instanceof ResultFolder) {
594 						Node tNode = ((ResultFolder) obj).getNode();
595 						if (tNode.getPrimaryNodeType().isNodeType(
596 								SlcTypes.SLC_RESULT_FOLDER)) {
597 							doIt = true;
598 							isResultFolder = true;
599 						}
600 					} else
601 						isResultFolder = false;
602 				} else
603 					isResultFolder = false;
604 
605 				if (!isResultFolder) {
606 					@SuppressWarnings("rawtypes")
607 					Iterator it = selection.iterator();
608 					while (it.hasNext()) {
609 						Object obj = it.next();
610 						if (obj instanceof SingleResultNode) {
611 							Node tNode = ((SingleResultNode) obj).getNode();
612 							if (tNode.getPrimaryNodeType().isNodeType(
613 									SlcTypes.SLC_TEST_RESULT)) {
614 								doIt = true;
615 							}
616 						}
617 					}
618 				}
619 
620 			} catch (RepositoryException re) {
621 				throw new SlcException(
622 						"unexpected error while validating drag source", re);
623 			}
624 			event.doit = doIt;
625 		}
626 
627 		public void dragSetData(DragSourceEvent event) {
628 			IStructuredSelection selection = (IStructuredSelection) resultTreeViewer
629 					.getSelection();
630 
631 			try {
632 				// specific case of a result folder
633 				if (isResultFolder) {
634 					Object obj = selection.getFirstElement();
635 					event.data = ((ResultFolder) obj).getNode().getIdentifier();
636 				} else {
637 					@SuppressWarnings("rawtypes")
638 					Iterator it = selection.iterator();
639 					StringBuilder nodes = new StringBuilder();
640 					while (it.hasNext()) {
641 						Object obj = it.next();
642 						if (obj instanceof SingleResultNode) {
643 							Node tNode = ((SingleResultNode) obj).getNode();
644 							if (tNode.getPrimaryNodeType().isNodeType(
645 									SlcTypes.SLC_TEST_RESULT)) {
646 								nodes.append(tNode.getIdentifier()).append(";");
647 							}
648 						}
649 					}
650 					event.data = nodes.toString();
651 				}
652 			} catch (RepositoryException re) {
653 				throw new SlcException("unexpected error while setting data",
654 						re);
655 			}
656 		}
657 
658 		public void dragFinished(DragSourceEvent event) {
659 			// refresh is done via observer
660 		}
661 	}
662 
663 	// Implementation of the Drop Listener
664 	protected class ViewDropListener extends ViewerDropAdapter {
665 		private Node targetParentNode = null;
666 
667 		public ViewDropListener(Viewer viewer) {
668 			super(viewer);
669 		}
670 
671 		@Override
672 		public boolean validateDrop(Object target, int operation,
673 				TransferData transferType) {
674 			boolean validDrop = false;
675 			try {
676 				// We can only drop under myResults
677 				Node tpNode = null;
678 				if (target instanceof SingleResultNode) {
679 					Node currNode = ((SingleResultNode) target).getNode();
680 					String pPath = currNode.getParent().getPath();
681 					if (pPath.startsWith(SlcJcrResultUtils
682 							.getMyResultsBasePath(session)))
683 						tpNode = currNode.getParent();
684 				} else if (target instanceof ResultFolder) {
685 					tpNode = ((ResultFolder) target).getNode();
686 				} else if (target instanceof ParentNodeFolder) {
687 					Node node = ((ParentNodeFolder) target).getNode();
688 					if (node.isNodeType(SlcTypes.SLC_MY_RESULT_ROOT_FOLDER))
689 						tpNode = ((ParentNodeFolder) target).getNode();
690 				}
691 
692 				if (tpNode != null) {
693 					targetParentNode = tpNode;
694 					validDrop = true;
695 				}
696 			} catch (RepositoryException re) {
697 				throw new SlcException(
698 						"unexpected error while validating drop target", re);
699 			}
700 			return validDrop;
701 		}
702 
703 		@Override
704 		public boolean performDrop(Object data) {
705 			// clear selection to prevent unwanted scrolling of the UI
706 			resultTreeViewer.setSelection(null);
707 			try {
708 				if (isResultFolder) {
709 					// Sanity check : we cannot move a folder to one of its sub
710 					// folder or neither move an object in the same parent
711 					// folder
712 					Node source = session.getNodeByIdentifier((String) data);
713 					if (targetParentNode.getPath().startsWith(source.getPath())
714 							|| source.getParent().getPath()
715 									.equals(targetParentNode.getPath()))
716 						return false;
717 
718 					// Move
719 					String sourcePath = source.getPath();
720 					String destPath = targetParentNode.getPath() + "/"
721 							+ source.getName();
722 					session.move(sourcePath, destPath);
723 					// Update passed status of the parent source Node
724 					ResultParentUtils.updatePassedStatus(
725 							session.getNode(JcrUtils.parentPath(sourcePath)),
726 							true);
727 					// Node target = session.getNode(destPath);
728 					session.save();
729 					return true;
730 				}
731 
732 				String[] datas = ((String) data).split(";");
733 				nodesToCopy: for (String id : datas) {
734 
735 					Node source = session.getNodeByIdentifier(id);
736 					String name;
737 					if (source.hasProperty(Property.JCR_TITLE))
738 						name = source.getProperty(Property.JCR_TITLE)
739 								.getString();
740 					else if (source.hasProperty(SlcNames.SLC_TEST_CASE))
741 						name = source.getProperty(SlcNames.SLC_TEST_CASE)
742 								.getString();
743 					else
744 						name = source.getName();
745 
746 					// Check if another copy of the same test instance already
747 					// exists at target
748 					NodeIterator ni = targetParentNode.getNodes();
749 					String slcUid = source.getProperty(SlcNames.SLC_UUID)
750 							.getString();
751 					while (ni.hasNext()) {
752 						Node curr = ni.nextNode();
753 						if (curr.hasProperty(SlcNames.SLC_UUID)
754 								&& slcUid.equals(curr.getProperty(
755 										SlcNames.SLC_UUID).getString())) {
756 							MessageDialog
757 									.openWarning(
758 											PlatformUI.getWorkbench()
759 													.getDisplay()
760 													.getActiveShell(),
761 											"Duplicated instance.",
762 											"An instance of the same test case ("
763 													+ name
764 													+ ") exists at destination.\n "
765 													+ "This item will not be neither copied nor moved.");
766 							continue nodesToCopy;
767 
768 						}
769 					}
770 
771 					Node target;
772 					boolean passedStatus = false;
773 					if (source.hasNode(SlcNames.SLC_AGGREGATED_STATUS))
774 						passedStatus = source
775 								.getNode(SlcNames.SLC_AGGREGATED_STATUS)
776 								.getProperty(SlcNames.SLC_SUCCESS).getBoolean();
777 
778 					boolean isActionUnderMyResult = source.getPath()
779 							.startsWith(
780 									SlcJcrResultUtils
781 											.getMyResultsBasePath(session));
782 
783 					if (!isActionUnderMyResult) {// Copy
784 						target = targetParentNode.addNode(source.getName(),
785 								source.getPrimaryNodeType().getName());
786 						JcrUtils.copy(source, target);
787 					} else {// move
788 						String sourcePath = source.getPath();
789 						String destPath = targetParentNode.getPath() + "/"
790 								+ name;
791 						session.move(sourcePath, destPath);
792 						// Update passed status of the parent source Node
793 						ResultParentUtils
794 								.updatePassedStatus(session.getNode(JcrUtils
795 										.parentPath(sourcePath)), true);
796 						target = session.getNode(destPath);
797 
798 					}
799 					if (!target.isNodeType(NodeType.MIX_TITLE))
800 						target.addMixin(NodeType.MIX_TITLE);
801 					target.setProperty(Property.JCR_TITLE, name);
802 					ResultParentUtils.updatePassedStatus(target.getParent(),
803 							passedStatus);
804 					session.save();
805 				}
806 			} catch (RepositoryException re) {
807 				throw new SlcException(
808 						"unexpected error while copying dropped node", re);
809 
810 			}
811 			return true;
812 		}
813 	}
814 
815 	class MyResultsObserver extends AsyncUiEventListener {
816 
817 		public MyResultsObserver(Display display) {
818 			super(display);
819 		}
820 
821 		@Override
822 		protected Boolean willProcessInUiThread(List<Event> events)
823 				throws RepositoryException {
824 			// unfiltered for the time being
825 			return true;
826 		}
827 
828 		protected void onEventInUiThread(List<Event> events)
829 				throws RepositoryException {
830 			List<Node> nodesToRefresh = new ArrayList<Node>();
831 
832 			for (Event event : events) {
833 				String parPath = JcrUtils.parentPath(event.getPath());
834 				if (session.nodeExists(parPath)) {
835 					Node node = session.getNode(parPath);
836 					if (!nodesToRefresh.contains(node)) {
837 						nodesToRefresh.add(node);
838 					}
839 				}
840 			}
841 
842 			// Update check nodes
843 			for (Node node : nodesToRefresh)
844 				jcrRefresh(node);
845 			refresh(null);
846 		}
847 	}
848 
849 	class AllResultsObserver extends AsyncUiEventListener {
850 
851 		public AllResultsObserver(Display display) {
852 			super(display);
853 		}
854 
855 		@Override
856 		protected Boolean willProcessInUiThread(List<Event> events)
857 				throws RepositoryException {
858 			// unfiltered for the time being
859 			return true;
860 		}
861 
862 		protected void onEventInUiThread(List<Event> events)
863 				throws RepositoryException {
864 			refresh(null);
865 			// if (lastSelectedSourceElementParent != null)
866 			// refresh(lastSelectedSourceElementParent);
867 		}
868 	}
869 
870 	class PropertiesContentProvider implements IStructuredContentProvider {
871 
872 		public void dispose() {
873 		}
874 
875 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
876 		}
877 
878 		public Object[] getElements(Object inputElement) {
879 			try {
880 				if (inputElement instanceof Node) {
881 					Node node = (Node) inputElement;
882 					if (node.isNodeType(SlcTypes.SLC_TEST_RESULT)) {
883 						List<Property> props = new ArrayList<Property>();
884 						if (node.hasProperty(SlcNames.SLC_TEST_CASE))
885 							props.add(node.getProperty(SlcNames.SLC_TEST_CASE));
886 						if (node.hasProperty(SlcNames.SLC_COMPLETED))
887 							props.add(node.getProperty(SlcNames.SLC_COMPLETED));
888 						if (node.hasNode(SlcNames.SLC_AGGREGATED_STATUS)) {
889 							Node status = node
890 									.getNode(SlcNames.SLC_AGGREGATED_STATUS);
891 							props.add(status.getProperty(SlcNames.SLC_SUCCESS));
892 							if (status.hasProperty(SlcNames.SLC_MESSAGE))
893 								props.add(status
894 										.getProperty(SlcNames.SLC_MESSAGE));
895 							if (status.hasProperty(SlcNames.SLC_ERROR_MESSAGE))
896 								props.add(status
897 										.getProperty(SlcNames.SLC_ERROR_MESSAGE));
898 						}
899 						return props.toArray();
900 					}
901 				}
902 				return new Object[] {};
903 
904 			} catch (RepositoryException e) {
905 				throw new SlcException("Cannot get element for "
906 						+ inputElement, e);
907 			}
908 		}
909 	}
910 
911 	class MySelectionChangedListener implements ISelectionChangedListener {
912 
913 		public void selectionChanged(SelectionChangedEvent event) {
914 			if (!event.getSelection().isEmpty()) {
915 				IStructuredSelection sel = (IStructuredSelection) event
916 						.getSelection();
917 				ResultParent firstItem = (ResultParent) sel.getFirstElement();
918 				if (firstItem instanceof SingleResultNode)
919 					propertiesViewer.setInput(((SingleResultNode) firstItem)
920 							.getNode());
921 				else
922 					propertiesViewer.setInput(null);
923 				// update cache for Drag & drop
924 				// lastSelectedTargetElement = firstItem;
925 				// lastSelectedSourceElement = firstItem;
926 				// lastSelectedSourceElementParent = (ResultParent) firstItem
927 				// .getParent();
928 				// String pPath = "";
929 				// try {
930 				//
931 				// if (firstItem instanceof ParentNodeFolder)
932 				// pPath = ((ParentNodeFolder) firstItem).getNode()
933 				// .getPath();
934 				// else if (firstItem instanceof SingleResultNode)
935 				// pPath = ((SingleResultNode) firstItem).getNode()
936 				// .getPath();
937 				// } catch (RepositoryException e) {
938 				// throw new SlcException(
939 				// "Unexpected error while checking parent UI tree", e);
940 				// }
941 				// if ((pPath.startsWith(SlcJcrResultUtils
942 				// .getMyResultsBasePath(session))))
943 				// isActionUnderMyResult = true;
944 				// else
945 				// isActionUnderMyResult = false;
946 			}
947 		}
948 	}
949 
950 	class ViewDoubleClickListener implements IDoubleClickListener {
951 		public void doubleClick(DoubleClickEvent evt) {
952 			processDoubleClick(evt);
953 		}
954 
955 	}
956 
957 	/* DEPENDENCY INJECTION */
958 	public void dispose() {
959 		// JcrUtils.unregisterQuietly(session.getWorkspace(), resultsObserver);
960 		JcrUtils.logoutQuietly(session);
961 		super.dispose();
962 	}
963 
964 	public void setRepository(Repository repository) {
965 		this.repository = repository;
966 	}
967 }