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