]> git.argeo.org Git - gpl/argeo-slc.git/blob - plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/views/JcrResultTreeView.java
Make execution editor more robust
[gpl/argeo-slc.git] / plugins / org.argeo.slc.client.ui / src / main / java / org / argeo / slc / client / ui / views / JcrResultTreeView.java
1 package org.argeo.slc.client.ui.views;
2
3 import java.util.ArrayList;
4 import java.util.Calendar;
5 import java.util.List;
6
7 import javax.jcr.Node;
8 import javax.jcr.NodeIterator;
9 import javax.jcr.Property;
10 import javax.jcr.PropertyIterator;
11 import javax.jcr.PropertyType;
12 import javax.jcr.RepositoryException;
13 import javax.jcr.Session;
14 import javax.jcr.Value;
15 import javax.jcr.nodetype.NodeType;
16 import javax.jcr.observation.Event;
17 import javax.jcr.observation.EventListener;
18 import javax.jcr.observation.ObservationManager;
19
20 import org.argeo.ArgeoException;
21 import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
22 import org.argeo.eclipse.ui.utils.CommandUtils;
23 import org.argeo.jcr.JcrUtils;
24 import org.argeo.jcr.UserJcrUtils;
25 import org.argeo.slc.SlcException;
26 import org.argeo.slc.client.ui.ClientUiPlugin;
27 import org.argeo.slc.client.ui.commands.AddResultFolder;
28 import org.argeo.slc.client.ui.model.ParentNodeFolder;
29 import org.argeo.slc.client.ui.model.ResultFolder;
30 import org.argeo.slc.client.ui.model.ResultParent;
31 import org.argeo.slc.client.ui.model.ResultParentUtils;
32 import org.argeo.slc.client.ui.model.SingleResultNode;
33 import org.argeo.slc.client.ui.model.VirtualFolder;
34 import org.argeo.slc.client.ui.providers.ResultTreeContentProvider;
35 import org.argeo.slc.client.ui.providers.ResultTreeLabelProvider;
36 import org.argeo.slc.jcr.SlcJcrResultUtils;
37 import org.argeo.slc.jcr.SlcNames;
38 import org.argeo.slc.jcr.SlcTypes;
39 import org.eclipse.jface.action.IMenuListener;
40 import org.eclipse.jface.action.IMenuManager;
41 import org.eclipse.jface.action.MenuManager;
42 import org.eclipse.jface.viewers.ColumnLabelProvider;
43 import org.eclipse.jface.viewers.DecoratingLabelProvider;
44 import org.eclipse.jface.viewers.ILabelDecorator;
45 import org.eclipse.jface.viewers.ISelectionChangedListener;
46 import org.eclipse.jface.viewers.IStructuredContentProvider;
47 import org.eclipse.jface.viewers.IStructuredSelection;
48 import org.eclipse.jface.viewers.SelectionChangedEvent;
49 import org.eclipse.jface.viewers.TableViewer;
50 import org.eclipse.jface.viewers.TableViewerColumn;
51 import org.eclipse.jface.viewers.TreeViewer;
52 import org.eclipse.jface.viewers.Viewer;
53 import org.eclipse.jface.viewers.ViewerDropAdapter;
54 import org.eclipse.swt.SWT;
55 import org.eclipse.swt.custom.SashForm;
56 import org.eclipse.swt.dnd.DND;
57 import org.eclipse.swt.dnd.DragSourceEvent;
58 import org.eclipse.swt.dnd.DragSourceListener;
59 import org.eclipse.swt.dnd.TextTransfer;
60 import org.eclipse.swt.dnd.Transfer;
61 import org.eclipse.swt.dnd.TransferData;
62 import org.eclipse.swt.layout.FillLayout;
63 import org.eclipse.swt.layout.GridData;
64 import org.eclipse.swt.layout.GridLayout;
65 import org.eclipse.swt.widgets.Composite;
66 import org.eclipse.swt.widgets.Display;
67 import org.eclipse.swt.widgets.Menu;
68 import org.eclipse.ui.ISharedImages;
69 import org.eclipse.ui.IWorkbenchWindow;
70 import org.eclipse.ui.part.ViewPart;
71
72 /** SLC generic JCR Result tree view. */
73 public class JcrResultTreeView extends ViewPart {
74 public final static String ID = ClientUiPlugin.ID + ".jcrResultTreeView";
75
76 // private final static Log log =
77 // LogFactory.getLog(JcrResultTreeView.class);
78
79 /* DEPENDENCY INJECTION */
80 private Session session;
81
82 // This page widgets
83 private TreeViewer resultTreeViewer;
84 private TableViewer propertiesViewer;
85
86 private EventListener resultsObserver = null;
87
88 private final static String[] observedNodeTypes = {
89 SlcTypes.SLC_TEST_RESULT, SlcTypes.SLC_RESULT_FOLDER,
90 NodeType.NT_UNSTRUCTURED };
91
92 // FIXME cache to ease refresh after D&D
93 private ResultParent lastSelectedElement;
94 private ResultParent lastSelectedElementParent;
95
96 /**
97 * To be overridden to adapt size of form and result frames.
98 */
99 protected int[] getWeights() {
100 return new int[] { 70, 30 };
101 }
102
103 @Override
104 public void createPartControl(Composite parent) {
105 parent.setLayout(new FillLayout());
106 // Main layout
107 SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
108 sashForm.setSashWidth(4);
109 sashForm.setLayout(new FillLayout());
110
111 // Create the tree on top of the view
112 Composite top = new Composite(sashForm, SWT.NONE);
113 GridLayout gl = new GridLayout(1, false);
114 top.setLayout(gl);
115 resultTreeViewer = createResultsTreeViewer(top);
116
117 // Create the property viewer on the bottom
118 Composite bottom = new Composite(sashForm, SWT.NONE);
119 bottom.setLayout(new GridLayout(1, false));
120 propertiesViewer = createPropertiesViewer(bottom);
121
122 sashForm.setWeights(getWeights());
123
124 // Refresh the view to initialize it
125 refresh(null);
126 }
127
128 // The main tree viewer
129 protected TreeViewer createResultsTreeViewer(Composite parent) {
130 int style = SWT.BORDER | SWT.MULTI;
131
132 TreeViewer viewer = new TreeViewer(parent, style);
133 viewer.getTree().setLayoutData(
134 new GridData(SWT.FILL, SWT.FILL, true, true));
135
136 viewer.setContentProvider(new ResultTreeContentProvider());
137
138 // Add label provider with label decorator
139 ResultTreeLabelProvider rtLblProvider = new ResultTreeLabelProvider();
140 ILabelDecorator decorator = ClientUiPlugin.getDefault().getWorkbench()
141 .getDecoratorManager().getLabelDecorator();
142 viewer.setLabelProvider(new DecoratingLabelProvider(rtLblProvider,
143 decorator));
144 // viewer.setLabelProvider(rtLblProvider);
145 getSite().setSelectionProvider(viewer);
146
147 // add drag & drop support
148 int operations = DND.DROP_COPY | DND.DROP_MOVE;
149 Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
150 viewer.addDragSupport(operations, tt, new ViewDragListener());
151 viewer.addDropSupport(operations, tt, new ViewDropListener(viewer));
152
153 // add context menu
154 MenuManager menuManager = new MenuManager();
155 Menu menu = menuManager.createContextMenu(viewer.getTree());
156 menuManager.addMenuListener(new IMenuListener() {
157 public void menuAboutToShow(IMenuManager manager) {
158 contextMenuAboutToShow(manager);
159 }
160 });
161 viewer.getTree().setMenu(menu);
162 getSite().registerContextMenu(menuManager, viewer);
163
164 // add change listener to display TestResult information in the property
165 // viewer
166 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
167 public void selectionChanged(SelectionChangedEvent event) {
168 if (!event.getSelection().isEmpty()) {
169 IStructuredSelection sel = (IStructuredSelection) event
170 .getSelection();
171 Object firstItem = sel.getFirstElement();
172 if (firstItem instanceof SingleResultNode)
173 propertiesViewer
174 .setInput(((SingleResultNode) firstItem)
175 .getNode());
176 else
177 propertiesViewer.setInput(null);
178 lastSelectedElement = (ResultParent) firstItem;
179 lastSelectedElementParent = (ResultParent) ((ResultParent) firstItem)
180 .getParent();
181 }
182 }
183 });
184 return viewer;
185 }
186
187 // Detailed property viewer
188 protected TableViewer createPropertiesViewer(Composite parent) {
189 propertiesViewer = new TableViewer(parent);
190 propertiesViewer.getTable().setLayoutData(
191 new GridData(SWT.FILL, SWT.FILL, true, true));
192 propertiesViewer.getTable().setHeaderVisible(true);
193 propertiesViewer.setContentProvider(new PropertiesContentProvider());
194 TableViewerColumn col = new TableViewerColumn(propertiesViewer,
195 SWT.NONE);
196 col.getColumn().setText("Name");
197 col.getColumn().setWidth(200);
198 col.setLabelProvider(new ColumnLabelProvider() {
199 public String getText(Object element) {
200 try {
201 return ((Property) element).getName();
202 } catch (RepositoryException e) {
203 throw new ArgeoException(
204 "Unexpected exception in label provider", e);
205 }
206 }
207 });
208 col = new TableViewerColumn(propertiesViewer, SWT.NONE);
209 col.getColumn().setText("Value");
210 col.getColumn().setWidth(400);
211 col.setLabelProvider(new ColumnLabelProvider() {
212 public String getText(Object element) {
213 try {
214 Property property = (Property) element;
215 if (property.getType() == PropertyType.BINARY)
216 return "<binary>";
217 else if (property.isMultiple()) {
218 StringBuffer buf = new StringBuffer("[");
219 Value[] values = property.getValues();
220 for (int i = 0; i < values.length; i++) {
221 if (i != 0)
222 buf.append(", ");
223 buf.append(values[i].getString());
224 }
225 buf.append(']');
226 return buf.toString();
227 } else
228 return property.getValue().getString();
229 } catch (RepositoryException e) {
230 throw new ArgeoException(
231 "Unexpected exception in label provider", e);
232 }
233 }
234 });
235 col = new TableViewerColumn(propertiesViewer, SWT.NONE);
236 col.getColumn().setText("Type");
237 col.getColumn().setWidth(200);
238 col.setLabelProvider(new ColumnLabelProvider() {
239 public String getText(Object element) {
240 try {
241 return PropertyType.nameFromValue(((Property) element)
242 .getType());
243 } catch (RepositoryException e) {
244 throw new ArgeoException(
245 "Unexpected exception in label provider", e);
246 }
247 }
248 });
249 propertiesViewer.setInput(getViewSite());
250 return propertiesViewer;
251 }
252
253 @Override
254 public void setFocus() {
255 }
256
257 /**
258 * refreshes the passed node and its corresponding subtree.
259 *
260 * @param node
261 * cannot be null
262 *
263 */
264 public boolean jcrRefresh(Node node) {
265 boolean isPassed = true;
266 try {
267 if (node.isNodeType(SlcTypes.SLC_TEST_RESULT)) {
268 isPassed = node.getNode(SlcNames.SLC_STATUS)
269 .getProperty(SlcNames.SLC_SUCCESS).getBoolean();
270 } else if (node.isNodeType(SlcTypes.SLC_RESULT_FOLDER)) {
271 NodeIterator ni = node.getNodes();
272 // quicker but wrong : refresh will stop as soon as a failed
273 // test is found and the whole tree won't be refreshed
274 // while (isPassed && ni.hasNext()){
275 while (ni.hasNext()) {
276 Node currChild = ni.nextNode();
277 isPassed = isPassed & jcrRefresh(currChild);
278 }
279 if (isPassed != node.getNode(SlcNames.SLC_STATUS)
280 .getProperty(SlcNames.SLC_SUCCESS).getBoolean()) {
281 node.getNode(SlcNames.SLC_STATUS).setProperty(
282 SlcNames.SLC_SUCCESS, isPassed);
283 node.getSession().save();
284 return isPassed;
285 }
286 } else
287 ; // do nothing
288 } catch (RepositoryException e) {
289 throw new SlcException("Cannot register listeners", e);
290 }
291 return isPassed;
292 }
293
294 /**
295 * refreshes the passed resultParent and its corresponding subtree. It
296 * refreshes the whole viewer if null is passed.
297 *
298 * @param ResultParent
299 *
300 */
301 public void refresh(ResultParent resultParent) {
302 if (resultParent == null) {
303 resultTreeViewer.setInput(initializeResultTree());
304 if (resultsObserver == null) {
305 try {
306 ObservationManager observationManager = session
307 .getWorkspace().getObservationManager();
308 resultsObserver = new ResultObserver(resultTreeViewer
309 .getTree().getDisplay());
310 observationManager.addEventListener(resultsObserver,
311 Event.NODE_ADDED | Event.NODE_REMOVED, UserJcrUtils
312 .getUserHome(session).getPath(), true,
313 null, observedNodeTypes, false);
314 } catch (RepositoryException e) {
315 throw new SlcException("Cannot register listeners", e);
316 }
317 }
318
319 } else {
320 // FIXME implement refresh for a specific ResultParent object.
321 if (resultParent instanceof ResultFolder) {
322 ResultFolder currFolder = (ResultFolder) resultParent;
323 jcrRefresh(currFolder.getNode());
324 currFolder.forceFullRefresh();
325 resultTreeViewer.refresh(lastSelectedElement);
326 }
327 }
328 }
329
330 private ResultParent[] initializeResultTree() {
331 try {
332 if (session.nodeExists(SlcJcrResultUtils
333 .getSlcResultsBasePath(session))) {
334 ResultParent[] roots = new ResultParent[5];
335
336 // My results
337 roots[0] = new ParentNodeFolder(null,
338 SlcJcrResultUtils.getMyResultParentNode(session),
339 "My results");
340
341 // today
342 Calendar cal = Calendar.getInstance();
343 String relPath = JcrUtils.dateAsPath(cal);
344 List<String> datePathes = new ArrayList<String>();
345 datePathes.add(relPath);
346 roots[1] = new VirtualFolder(null,
347 ResultParentUtils.getResultsForDates(session,
348 datePathes), "Today");
349
350 // Yesterday
351 cal = Calendar.getInstance();
352 cal.add(Calendar.DAY_OF_YEAR, -1);
353 relPath = JcrUtils.dateAsPath(cal);
354 datePathes = new ArrayList<String>();
355 datePathes.add(relPath);
356 roots[2] = new VirtualFolder(null,
357 ResultParentUtils.getResultsForDates(session,
358 datePathes), "Yesterday");
359 // Last 7 days
360
361 cal = Calendar.getInstance();
362 datePathes = new ArrayList<String>();
363
364 for (int i = 0; i < 7; i++) {
365 cal.add(Calendar.DAY_OF_YEAR, -i);
366 relPath = JcrUtils.dateAsPath(cal);
367 datePathes.add(relPath);
368 }
369 roots[3] = new VirtualFolder(null,
370 ResultParentUtils.getResultsForDates(session,
371 datePathes), "Last 7 days");
372
373 // All results
374 Node otherResultsPar = session.getNode(SlcJcrResultUtils
375 .getSlcResultsBasePath(session));
376 roots[4] = new ParentNodeFolder(null, otherResultsPar,
377 "All results");
378 return roots;
379 } else
380 // no test has yet been processed, we leave the viewer blank
381 return null;
382 } catch (RepositoryException re) {
383 throw new ArgeoException(
384 "Unexpected error while initializing ResultTree.", re);
385 }
386 }
387
388 // Manage context menu
389 /**
390 * Defines the commands that will pop up in the context menu.
391 **/
392 protected void contextMenuAboutToShow(IMenuManager menuManager) {
393 IWorkbenchWindow window = ClientUiPlugin.getDefault().getWorkbench()
394 .getActiveWorkbenchWindow();
395
396 // Building conditions
397 IStructuredSelection selection = (IStructuredSelection) resultTreeViewer
398 .getSelection();
399 boolean isMyResultFolder = false;
400 if (selection.size() == 1) {
401 Object obj = selection.getFirstElement();
402 try {
403 if (obj instanceof ResultFolder
404 && (((ResultFolder) obj).getNode())
405 .isNodeType(SlcTypes.SLC_RESULT_FOLDER))
406 isMyResultFolder = true;
407 else if (obj instanceof ParentNodeFolder
408 && (((ParentNodeFolder) obj).getNode().getPath()
409 .startsWith(SlcJcrResultUtils
410 .getMyResultsBasePath(session))))
411 isMyResultFolder = true;
412 } catch (RepositoryException re) {
413 throw new SlcException(
414 "unexpected error while building condition for context menu",
415 re);
416 }
417 }
418 // Effective Refresh
419 CommandUtils.refreshCommand(menuManager, window, AddResultFolder.ID,
420 AddResultFolder.DEFAULT_LABEL,
421 ClientUiPlugin.getDefault().getWorkbench().getSharedImages()
422 .getImageDescriptor(ISharedImages.IMG_OBJ_ADD),
423 isMyResultFolder);
424 }
425
426 /* INNER CLASSES */
427 class ViewDragListener implements DragSourceListener {
428
429 public void dragStart(DragSourceEvent event) {
430 // Check if the drag action should start.
431
432 IStructuredSelection selection = (IStructuredSelection) resultTreeViewer
433 .getSelection();
434 boolean doIt = false;
435 // only one node at a time for the time being.
436 if (selection.size() == 1) {
437 Object obj = selection.getFirstElement();
438 if (obj instanceof SingleResultNode) {
439 Node tNode = ((SingleResultNode) obj).getNode();
440 try {
441 if (tNode.getPrimaryNodeType().isNodeType(
442 SlcTypes.SLC_TEST_RESULT)
443 && (tNode.getPath()
444 .startsWith(SlcJcrResultUtils
445 .getSlcResultsBasePath(session))))
446 doIt = true;
447 } catch (RepositoryException re) {
448 throw new SlcException(
449 "unexpected error while validating drag source",
450 re);
451 }
452 }
453 }
454 event.doit = doIt;
455 }
456
457 public void dragSetData(DragSourceEvent event) {
458 IStructuredSelection selection = (IStructuredSelection) resultTreeViewer
459 .getSelection();
460 Object obj = selection.getFirstElement();
461 if (obj instanceof SingleResultNode) {
462 Node first = ((SingleResultNode) obj).getNode();
463 try {
464 event.data = first.getIdentifier();
465 } catch (RepositoryException re) {
466 throw new SlcException(
467 "unexpected error while setting data", re);
468 }
469 }
470 }
471
472 public void dragFinished(DragSourceEvent event) {
473 // implement here tree refresh in case of a move.
474 }
475 }
476
477 // Implementation of the Drop Listener
478 protected class ViewDropListener extends ViewerDropAdapter {
479
480 private Node currParentNode = null;
481
482 public ViewDropListener(Viewer viewer) {
483 super(viewer);
484 }
485
486 @Override
487 public boolean validateDrop(Object target, int operation,
488 TransferData transferType) {
489 boolean validDrop = false;
490 try {
491 // We can only drop under myResults
492 Node targetParentNode = null;
493 if (target instanceof ResultFolder) {
494 targetParentNode = ((ResultFolder) target).getNode();
495 } else if (target instanceof ParentNodeFolder) {
496 if ((((ParentNodeFolder) target).getNode().getPath()
497 .startsWith(SlcJcrResultUtils
498 .getMyResultsBasePath(session))))
499 targetParentNode = ((ParentNodeFolder) target)
500 .getNode();
501 } else if (target instanceof SingleResultNode) {
502 Node currNode = ((SingleResultNode) target).getNode();
503 if (currNode
504 .getParent()
505 .getPath()
506 .startsWith(
507 SlcJcrResultUtils
508 .getMyResultsBasePath(session)))
509 targetParentNode = currNode.getParent();
510 }
511 if (targetParentNode != null) {
512 currParentNode = targetParentNode;
513 validDrop = true;
514 // FIXME
515 lastSelectedElement = (ResultParent) target;
516 lastSelectedElementParent = (ResultParent) ((ResultParent) target)
517 .getParent();
518 }
519 } catch (RepositoryException re) {
520 throw new SlcException(
521 "unexpected error while validating drop target", re);
522 }
523 return validDrop;
524 }
525
526 @Override
527 public boolean performDrop(Object data) {
528
529 try {
530 Node source = session.getNodeByIdentifier((String) data);
531 Node target = currParentNode.addNode(source.getName(), source
532 .getPrimaryNodeType().getName());
533 JcrUtils.copy(source, target);
534 ResultParentUtils
535 .updatePassedStatus(target,
536 target.getNode(SlcNames.SLC_STATUS)
537 .getProperty(SlcNames.SLC_SUCCESS)
538 .getBoolean());
539 target.getSession().save();
540 } catch (RepositoryException re) {
541 throw new SlcException(
542 "unexpected error while copying dropped node", re);
543 }
544 return true;
545 }
546 }
547
548 class ResultObserver extends AsyncUiEventListener {
549
550 public ResultObserver(Display display) {
551 super(display);
552 }
553
554 @Override
555 protected Boolean willProcessInUiThread(List<Event> events)
556 throws RepositoryException {
557 // unfiltered for the time being
558 return true;
559 // for (Event event : events) {
560 // getLog().debug("Received event " + event);
561 // int eventType = event.getType();
562 // if (eventType == Event.NODE_REMOVED)
563 // ;//return true;
564 // String path = event.getPath();
565 // int index = path.lastIndexOf('/');
566 // String propertyName = path.substring(index + 1);
567 // if (propertyName.equals(SlcNames.SLC_COMPLETED)
568 // || propertyName.equals(SlcNames.SLC_UUID)) {
569 // ;//return true;
570 // }
571 // }
572 // return false;
573 }
574
575 protected void onEventInUiThread(List<Event> events)
576 throws RepositoryException {
577
578 for (Event event : events) {
579 getLog().debug("Received event " + event);
580 int eventType = event.getType();
581 if (eventType == Event.NODE_REMOVED) {
582 String path = event.getPath();
583 int index = path.lastIndexOf('/');
584 String parPath = path.substring(0, index + 1);
585 if (session.nodeExists(parPath)) {
586 Node currNode = session.getNode(parPath);
587 if (currNode.isNodeType(NodeType.NT_UNSTRUCTURED)) {
588 refresh(null);
589 jcrRefresh(currNode);
590 resultTreeViewer.refresh(true);
591 resultTreeViewer.expandToLevel(
592 lastSelectedElementParent, 1);
593
594 }
595 }
596 } else if (eventType == Event.NODE_ADDED) {
597 String path = event.getPath();
598 if (session.nodeExists(path)) {
599 Node currNode = session.getNode(path);
600 if (currNode.isNodeType(SlcTypes.SLC_DIFF_RESULT)
601 || currNode
602 .isNodeType(SlcTypes.SLC_RESULT_FOLDER)) {
603 refresh(null);
604 resultTreeViewer.expandToLevel(lastSelectedElement,
605 1);
606 }
607 }
608 }
609 // String path = event.getPath();
610 // int index = path.lastIndexOf('/');
611 // String propertyName = path.substring(index + 1);
612 // if (propertyName.equals(SlcNames.SLC_COMPLETED)
613 // || propertyName.equals(SlcNames.SLC_UUID)) {
614 // }
615 }
616
617 // FIXME implement correct behaviour. treeViewer selection is
618 // disposed by the drag & drop.
619 // resultTreeViewer.refresh();
620 // refresh(null);
621 // log.warn("Implement refresh.");
622 }
623 }
624
625 class PropertiesContentProvider implements IStructuredContentProvider {
626 // private JcrItemsComparator itemComparator = new JcrItemsComparator();
627
628 public void dispose() {
629 }
630
631 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
632 }
633
634 public Object[] getElements(Object inputElement) {
635 try {
636 if (inputElement instanceof Node) {
637 List<Property> props = new ArrayList<Property>();
638 PropertyIterator pit = ((Node) inputElement)
639 .getProperties();
640 while (pit.hasNext())
641 props.add(pit.nextProperty());
642 return props.toArray();
643 }
644 return new Object[] {};
645 } catch (RepositoryException e) {
646 throw new ArgeoException("Cannot get element for "
647 + inputElement, e);
648 }
649 }
650 }
651
652 /* DEPENDENCY INJECTION */
653 public void setSession(Session session) {
654 this.session = session;
655 }
656
657 }