]> git.argeo.org Git - gpl/argeo-slc.git/blob - eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessBuilderPage.java
UI can set parameters
[gpl/argeo-slc.git] / eclipse / plugins / org.argeo.slc.client.ui / src / main / java / org / argeo / slc / client / ui / editors / ProcessBuilderPage.java
1 package org.argeo.slc.client.ui.editors;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5 import java.util.List;
6 import java.util.UUID;
7
8 import javax.jcr.Node;
9 import javax.jcr.NodeIterator;
10 import javax.jcr.Property;
11 import javax.jcr.RepositoryException;
12 import javax.jcr.Session;
13 import javax.jcr.nodetype.NodeType;
14 import javax.jcr.observation.Event;
15 import javax.jcr.observation.EventIterator;
16 import javax.jcr.observation.EventListener;
17 import javax.jcr.observation.ObservationManager;
18
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.argeo.ArgeoException;
22 import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
23 import org.argeo.jcr.JcrUtils;
24 import org.argeo.slc.SlcException;
25 import org.argeo.slc.client.ui.SlcImages;
26 import org.argeo.slc.core.execution.PrimitiveUtils;
27 import org.argeo.slc.execution.ExecutionProcess;
28 import org.argeo.slc.jcr.SlcJcrUtils;
29 import org.argeo.slc.jcr.SlcNames;
30 import org.argeo.slc.jcr.SlcTypes;
31 import org.eclipse.jface.viewers.CellEditor;
32 import org.eclipse.jface.viewers.ColumnLabelProvider;
33 import org.eclipse.jface.viewers.ColumnViewer;
34 import org.eclipse.jface.viewers.EditingSupport;
35 import org.eclipse.jface.viewers.ISelectionChangedListener;
36 import org.eclipse.jface.viewers.IStructuredContentProvider;
37 import org.eclipse.jface.viewers.IStructuredSelection;
38 import org.eclipse.jface.viewers.ITreeContentProvider;
39 import org.eclipse.jface.viewers.SelectionChangedEvent;
40 import org.eclipse.jface.viewers.StructuredSelection;
41 import org.eclipse.jface.viewers.TableViewer;
42 import org.eclipse.jface.viewers.TableViewerColumn;
43 import org.eclipse.jface.viewers.TextCellEditor;
44 import org.eclipse.jface.viewers.TreeViewer;
45 import org.eclipse.jface.viewers.Viewer;
46 import org.eclipse.jface.viewers.ViewerDropAdapter;
47 import org.eclipse.swt.SWT;
48 import org.eclipse.swt.custom.SashForm;
49 import org.eclipse.swt.dnd.DND;
50 import org.eclipse.swt.dnd.TextTransfer;
51 import org.eclipse.swt.dnd.Transfer;
52 import org.eclipse.swt.dnd.TransferData;
53 import org.eclipse.swt.events.SelectionEvent;
54 import org.eclipse.swt.events.SelectionListener;
55 import org.eclipse.swt.graphics.Image;
56 import org.eclipse.swt.layout.FillLayout;
57 import org.eclipse.swt.layout.GridData;
58 import org.eclipse.swt.layout.GridLayout;
59 import org.eclipse.swt.layout.RowData;
60 import org.eclipse.swt.layout.RowLayout;
61 import org.eclipse.swt.widgets.Button;
62 import org.eclipse.swt.widgets.Composite;
63 import org.eclipse.swt.widgets.Label;
64 import org.eclipse.swt.widgets.Table;
65 import org.eclipse.ui.IWorkbenchPage;
66 import org.eclipse.ui.PlatformUI;
67 import org.eclipse.ui.forms.AbstractFormPart;
68 import org.eclipse.ui.forms.IManagedForm;
69 import org.eclipse.ui.forms.editor.FormPage;
70 import org.eclipse.ui.forms.widgets.FormToolkit;
71 import org.eclipse.ui.forms.widgets.ScrolledForm;
72
73 public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes {
74 public final static String ID = "processBuilderPage";
75 private final static Log log = LogFactory.getLog(ProcessBuilderPage.class);
76
77 private Node processNode;
78
79 private TreeViewer flowsViewer;
80 private TableViewer valuesViewer;
81 private Label statusLabel;
82 private Button run;
83 private Button remove;
84 private Button clear;
85
86 private AbstractFormPart formPart;
87 private EventListener statusObserver;
88
89 public ProcessBuilderPage(ProcessEditor editor, Node processNode) {
90 super(editor, ID, "Definition");
91 this.processNode = processNode;
92 }
93
94 @Override
95 protected void createFormContent(IManagedForm mf) {
96 try {
97 ScrolledForm form = mf.getForm();
98 form.setExpandHorizontal(true);
99 form.setExpandVertical(true);
100 form.setText("Process " + processNode.getName());
101 GridLayout mainLayout = new GridLayout(1, true);
102 form.getBody().setLayout(mainLayout);
103
104 createControls(form.getBody());
105 createBuilder(form.getBody());
106
107 // form
108 formPart = new AbstractFormPart() {
109
110 };
111 getManagedForm().addPart(formPart);
112
113 // observation
114 statusObserver = new AsyncUiEventListener() {
115 protected void onEventInUiThread(EventIterator events) {
116 statusChanged();
117 }
118 };
119 ObservationManager observationManager = processNode.getSession()
120 .getWorkspace().getObservationManager();
121 observationManager.addEventListener(statusObserver,
122 Event.PROPERTY_CHANGED, processNode.getPath(), true, null,
123 null, false);
124
125 // add initial flows
126 addInitialFlows();
127
128 } catch (RepositoryException e) {
129 throw new ArgeoException("Cannot create form content", e);
130 }
131 }
132
133 protected void createControls(Composite parent) {
134 FormToolkit tk = getManagedForm().getToolkit();
135
136 Composite controls = tk.createComposite(parent);
137 controls.setLayout(new RowLayout());
138 controls.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
139
140 run = tk.createButton(controls, null, SWT.PUSH);
141 run.setToolTipText("Run");
142 run.setImage(SlcImages.LAUNCH);
143 run.addSelectionListener(new SelectionListener() {
144 public void widgetSelected(SelectionEvent e) {
145 if (isFinished(getProcessStatus())) {
146 relaunch();
147 } else {
148 ((ProcessEditor) getEditor()).process();
149 }
150 }
151
152 public void widgetDefaultSelected(SelectionEvent e) {
153 widgetSelected(e);
154 }
155 });
156
157 remove = tk.createButton(controls, null, SWT.PUSH);
158 remove.setImage(SlcImages.REMOVE_ONE);
159 remove.setToolTipText("Remove selected flows");
160 remove.addSelectionListener(new SelectionListener() {
161 public void widgetSelected(SelectionEvent e) {
162 removeSelectedFlows();
163 }
164
165 public void widgetDefaultSelected(SelectionEvent e) {
166 widgetSelected(e);
167 }
168 });
169
170 clear = tk.createButton(controls, null, SWT.PUSH);
171 clear.setImage(SlcImages.REMOVE_ALL);
172 clear.setToolTipText("Clear all flows");
173 clear.addSelectionListener(new SelectionListener() {
174 public void widgetSelected(SelectionEvent e) {
175 removeAllFlows();
176 }
177
178 public void widgetDefaultSelected(SelectionEvent e) {
179 widgetSelected(e);
180 }
181 });
182
183 Composite statusComposite = tk.createComposite(controls);
184 RowData rowData = new RowData();
185 rowData.width = 100;
186 rowData.height = 16;
187 statusComposite.setLayoutData(rowData);
188 statusComposite.setLayout(new FillLayout());
189 statusLabel = tk.createLabel(statusComposite, getProcessStatus());
190
191 // make sure all controls are in line with status
192 statusChanged();
193 }
194
195 protected void createBuilder(Composite parent) {
196 FormToolkit tk = getManagedForm().getToolkit();
197 SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL);
198 sashForm.setSashWidth(4);
199 GridData sahFormGd = new GridData(SWT.FILL, SWT.FILL, true, true);
200 sahFormGd.widthHint = 400;
201 sashForm.setLayoutData(sahFormGd);
202
203 Composite flowsComposite = tk.createComposite(sashForm);
204 flowsComposite.setLayout(new GridLayout(1, false));
205
206 flowsViewer = new TreeViewer(flowsComposite);
207 flowsViewer.getTree().setLayoutData(
208 new GridData(SWT.FILL, SWT.FILL, true, true));
209 flowsViewer.setLabelProvider(new FlowsLabelProvider());
210 flowsViewer.setContentProvider(new FlowsContentProvider());
211 flowsViewer.addSelectionChangedListener(new FlowsSelectionListener());
212
213 int operations = DND.DROP_COPY | DND.DROP_MOVE;
214 Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
215 flowsViewer.addDropSupport(operations, tt, new FlowsDropListener(
216 flowsViewer));
217
218 flowsViewer.setInput(getEditorSite());
219 flowsViewer.setInput(processNode);
220
221 Composite valuesComposite = tk.createComposite(sashForm);
222 valuesComposite.setLayout(new GridLayout(1, false));
223
224 valuesViewer = new TableViewer(valuesComposite);
225 GridData valuedGd = new GridData(SWT.FILL, SWT.FILL, true, true);
226 // valuedGd.widthHint = 200;
227 valuesViewer.getTable().setLayoutData(valuedGd);
228 valuesViewer.setContentProvider(new ValuesContentProvider());
229 initializeValuesViewer(valuesViewer);
230 sashForm.setWeights(getWeights());
231 valuesViewer.setInput(getEditorSite());
232 }
233
234 /** Creates the columns of the values viewer */
235 protected void initializeValuesViewer(TableViewer viewer) {
236 String[] titles = { "Name", "Value" };
237 int[] bounds = { 200, 100 };
238
239 for (int i = 0; i < titles.length; i++) {
240 TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
241 column.getColumn().setText(titles[i]);
242 column.getColumn().setWidth(bounds[i]);
243 column.getColumn().setResizable(true);
244 column.getColumn().setMoveable(true);
245 if (i == 0) {
246 column.setLabelProvider(new ColumnLabelProvider() {
247 public String getText(Object element) {
248 try {
249 Node specAttrNode = (Node) element;
250 return specAttrNode.getName();
251 } catch (RepositoryException e) {
252 throw new SlcException("Cannot get value", e);
253 }
254 }
255 });
256 } else if (i == 1) {
257 column.setLabelProvider(new ColumnLabelProvider() {
258 public String getText(Object element) {
259 Object obj = getAttributeSpecValue((Node) element);
260 return obj != null ? obj.toString() : "";
261 }
262 });
263 column.setEditingSupport(new ValuesEditingSupport(viewer));
264 }
265
266 }
267 Table table = viewer.getTable();
268 table.setHeaderVisible(false);
269 table.setLinesVisible(true);
270 }
271
272 protected int[] getWeights() {
273 return new int[] { 50, 50 };
274 }
275
276 /*
277 * CONTROLLERS
278 */
279 /** Opens a new editor with a copy of this process */
280 protected void relaunch() {
281 try {
282 Node duplicatedNode = duplicateProcess();
283 IWorkbenchPage activePage = PlatformUI.getWorkbench()
284 .getActiveWorkbenchWindow().getActivePage();
285 activePage.openEditor(
286 new ProcessEditorInput(duplicatedNode.getPath()),
287 ProcessEditor.ID);
288 getEditor().close(false);
289 } catch (Exception e1) {
290 throw new SlcException("Cannot relaunch " + processNode, e1);
291 }
292 }
293
294 /** Duplicates the process */
295 protected Node duplicateProcess() {
296 try {
297 Session session = processNode.getSession();
298 String uuid = UUID.randomUUID().toString();
299 String destPath = SlcJcrUtils.createExecutionProcessPath(uuid);
300 Node newNode = JcrUtils.mkdirs(session, destPath, SLC_PROCESS);
301 JcrUtils.copy(processNode, newNode);
302 // session.getWorkspace().copy(processNode.getPath(), destPath);
303 // Node newNode = session.getNode(destPath);
304 // make sure that we kept the mixins
305 // newNode.addMixin(NodeType.MIX_CREATED);
306 // newNode.addMixin(NodeType.MIX_LAST_MODIFIED);
307 newNode.setProperty(SLC_UUID, uuid);
308 newNode.setProperty(SLC_STATUS, ExecutionProcess.INITIALIZED);
309 session.save();
310 return newNode;
311 } catch (RepositoryException e) {
312 throw new SlcException("Cannot duplicate process", e);
313 }
314 }
315
316 /** Reflects a status change */
317 protected void statusChanged() {
318 String status = getProcessStatus();
319 statusLabel.setText(status);
320 Boolean isEditable = isEditable(status);
321 run.setEnabled(isEditable);
322 remove.setEnabled(isEditable);
323 clear.setEnabled(isEditable);
324 // flowsViewer.getTree().setEnabled(isEditable);
325 if (status.equals(ExecutionProcess.COMPLETED)
326 || status.equals(ExecutionProcess.ERROR)) {
327 run.setEnabled(true);
328 run.setImage(SlcImages.RELAUNCH);
329 run.setToolTipText("Relaunch");
330 }
331 }
332
333 /** Adds initial flows from the editor input if any */
334 protected void addInitialFlows() {
335 for (String path : ((ProcessEditorInput) getEditorInput())
336 .getInitialFlowPaths()) {
337 addFlow(path);
338 }
339 }
340
341 /**
342 * Adds a new flow.
343 *
344 * @param path
345 * the path of the flow
346 */
347 public void addFlow(String path) {
348 try {
349 Node flowNode = processNode.getSession().getNode(path);
350 Node realizedFlowNode = processNode.getNode(SLC_FLOW).addNode(
351 SLC_FLOW);
352 realizedFlowNode.addMixin(SLC_REALIZED_FLOW);
353 Node address = realizedFlowNode.addNode(SLC_ADDRESS,
354 NodeType.NT_ADDRESS);
355 address.setProperty(Property.JCR_PATH, path);
356
357 // copy spec attributes
358 Node specAttrsBase;
359 if (flowNode.hasProperty(SLC_SPEC))
360 specAttrsBase = flowNode.getProperty(SLC_SPEC).getNode();
361 else
362 specAttrsBase = flowNode;
363
364 specAttrs: for (NodeIterator nit = specAttrsBase.getNodes(); nit
365 .hasNext();) {
366 Node specAttrNode = nit.nextNode();
367 if (!specAttrNode
368 .isNodeType(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE))
369 continue specAttrs;
370 Node realizedAttrNode = realizedFlowNode.addNode(specAttrNode
371 .getName());
372 JcrUtils.copy(specAttrNode, realizedAttrNode);
373 }
374
375 flowsViewer.refresh();
376 formPart.markDirty();
377 } catch (RepositoryException e) {
378 throw new SlcException("Cannot drop " + path, e);
379 }
380 }
381
382 @SuppressWarnings("unchecked")
383 protected void removeSelectedFlows() {
384 if (!flowsViewer.getSelection().isEmpty()) {
385 Iterator<Object> it = ((StructuredSelection) flowsViewer
386 .getSelection()).iterator();
387 while (it.hasNext()) {
388 Node node = (Node) it.next();
389 try {
390 node.remove();
391 } catch (RepositoryException e) {
392 throw new ArgeoException("Cannot remove " + node, e);
393 }
394 }
395 flowsViewer.refresh();
396 formPart.markDirty();
397 }
398 }
399
400 protected void removeAllFlows() {
401 try {
402 for (NodeIterator nit = processNode.getNode(SLC_FLOW).getNodes(); nit
403 .hasNext();) {
404 nit.nextNode().remove();
405 }
406 flowsViewer.refresh();
407 formPart.markDirty();
408 } catch (RepositoryException e) {
409 throw new ArgeoException("Cannot remove flows from " + processNode,
410 e);
411 }
412 }
413
414 public void commit(Boolean onSave) {
415 if (onSave)
416 statusLabel.setText(getProcessStatus());
417 formPart.commit(onSave);
418 }
419
420 /*
421 * STATE
422 */
423 protected String getProcessStatus() {
424 try {
425 return processNode.getProperty(SLC_STATUS).getString();
426 } catch (RepositoryException e) {
427 throw new SlcException("Cannot retrieve status for " + processNode,
428 e);
429 }
430 }
431
432 /** Optimization so that we don't call the node each time */
433 protected Boolean isEditable(String status) {
434 return status.equals(ExecutionProcess.NEW)
435 || status.equals(ExecutionProcess.INITIALIZED);
436 }
437
438 protected Boolean isFinished(String status) {
439 return status.equals(ExecutionProcess.COMPLETED)
440 || status.equals(ExecutionProcess.ERROR);
441 }
442
443 /*
444 * LIFECYCLE
445 */
446 @Override
447 public void dispose() {
448 JcrUtils.unregisterQuietly(processNode, statusObserver);
449 super.dispose();
450 }
451
452 /*
453 * UTILITIES
454 */
455 protected static Object getAttributeSpecValue(Node specAttrNode) {
456 try {
457 if (specAttrNode.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) {
458 if (!specAttrNode.hasProperty(SLC_VALUE))
459 return null;
460 String type = specAttrNode.getProperty(SLC_TYPE).getString();
461 // TODO optimize based on data type?
462 Object value = PrimitiveUtils.convert(type, specAttrNode
463 .getProperty(SLC_VALUE).getString());
464 log.debug(specAttrNode + ", type=" + type + ", value=" + value);
465 return value;
466 }
467 return null;
468 } catch (RepositoryException e) {
469 throw new SlcException("Cannot get value", e);
470 }
471
472 }
473
474 /*
475 * FLOWS SUBCLASSES
476 */
477 static class FlowsContentProvider implements ITreeContentProvider {
478 public Object[] getElements(Object obj) {
479 if (!(obj instanceof Node))
480 return new Object[0];
481
482 try {
483 Node node = (Node) obj;
484 List<Node> children = new ArrayList<Node>();
485 for (NodeIterator nit = node.getNode(SLC_FLOW).getNodes(); nit
486 .hasNext();)
487 children.add(nit.nextNode());
488 return children.toArray();
489 } catch (RepositoryException e) {
490 throw new SlcException("Cannot list children of " + obj, e);
491 }
492 }
493
494 public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
495 }
496
497 public void dispose() {
498 }
499
500 public Object[] getChildren(Object parentElement) {
501 // no children for the time being
502 return null;
503 }
504
505 public Object getParent(Object element) {
506 return null;
507 }
508
509 public boolean hasChildren(Object element) {
510 return false;
511 }
512
513 }
514
515 static class FlowsLabelProvider extends ColumnLabelProvider {
516
517 @Override
518 public String getText(Object element) {
519 Node node = (Node) element;
520 try {
521 if (node.isNodeType(SLC_REALIZED_FLOW)) {
522 if (node.hasNode(SLC_ADDRESS)) {
523 String path = node.getNode(SLC_ADDRESS)
524 .getProperty(Property.JCR_PATH).getString();
525 return SlcJcrUtils.flowExecutionModuleName(path) + ":"
526 + SlcJcrUtils.flowRelativePath(path);
527 }
528 }
529 } catch (RepositoryException e) {
530 throw new SlcException("Cannot display " + element, e);
531 }
532 return super.getText(element);
533 }
534
535 @Override
536 public Image getImage(Object element) {
537 Node node = (Node) element;
538 try {
539 if (node.isNodeType(SLC_REALIZED_FLOW)) {
540 return SlcImages.FLOW;
541 }
542 } catch (RepositoryException e) {
543 throw new SlcException("Cannot display " + element, e);
544 }
545 return super.getImage(element);
546 }
547
548 }
549
550 /** Parameter view is updated each time a new line is selected */
551 class FlowsSelectionListener implements ISelectionChangedListener {
552 public void selectionChanged(SelectionChangedEvent evt) {
553 if (evt.getSelection().isEmpty()) {
554 valuesViewer.setInput(getEditorSite());
555 return;
556 }
557 Node realizedFlowNode = (Node) ((IStructuredSelection) evt
558 .getSelection()).getFirstElement();
559 valuesViewer.setInput(realizedFlowNode);
560 }
561 }
562
563 class FlowsDropListener extends ViewerDropAdapter {
564
565 public FlowsDropListener(Viewer viewer) {
566 super(viewer);
567 }
568
569 @Override
570 public boolean performDrop(Object data) {
571 String path = data.toString();
572 addFlow(path);
573 return true;
574 }
575
576 @Override
577 public boolean validateDrop(Object target, int operation,
578 TransferData transferType) {
579 return isEditable(getProcessStatus());
580 }
581 }
582
583 /*
584 * VALUES SUBCLASSES
585 */
586 static class ValuesContentProvider implements IStructuredContentProvider {
587
588 public Object[] getElements(Object inputElement) {
589 if (!(inputElement instanceof Node))
590 return new Object[0];
591
592 try {
593 Node realizedFlowNode = (Node) inputElement;
594 List<Node> specAttributes = new ArrayList<Node>();
595 specAttrs: for (NodeIterator nit = realizedFlowNode.getNodes(); nit
596 .hasNext();) {
597 Node specAttrNode = nit.nextNode();
598 if (!specAttrNode
599 .isNodeType(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE))
600 continue specAttrs;
601 specAttributes.add(specAttrNode);
602 }
603 return specAttributes.toArray();
604 } catch (RepositoryException e) {
605 throw new SlcException("Cannot get elements", e);
606 }
607 }
608
609 public void dispose() {
610 }
611
612 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
613 }
614 }
615
616 class ValuesEditingSupport extends EditingSupport {
617 private final TableViewer tableViewer;
618
619 public ValuesEditingSupport(ColumnViewer viewer) {
620 super(viewer);
621 tableViewer = (TableViewer) viewer;
622 }
623
624 @Override
625 protected CellEditor getCellEditor(Object element) {
626 try {
627 Node specAttrNode = (Node) element;
628 if (specAttrNode
629 .isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE))
630 return new TextCellEditor(tableViewer.getTable());
631 return null;
632 } catch (RepositoryException e) {
633 throw new SlcException("Cannot get celle editor", e);
634 }
635 }
636
637 @Override
638 protected boolean canEdit(Object element) {
639 try {
640 Node specAttrNode = (Node) element;
641 return !(specAttrNode.getProperty(SLC_IS_IMMUTABLE)
642 .getBoolean() || specAttrNode.getProperty(
643 SLC_IS_CONSTANT).getBoolean())
644 && specAttrNode
645 .isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE);
646 } catch (RepositoryException e) {
647 throw new SlcException("Cannot check canEdit", e);
648 }
649 }
650
651 @Override
652 protected Object getValue(Object element) {
653 Node specAttrNode = (Node) element;
654 try {
655 Object value = getAttributeSpecValue(specAttrNode);
656 if (value == null)
657 throw new SlcException("Unsupported attribute " + element);
658 if (specAttrNode
659 .isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE))
660 return value.toString();
661 return value;
662 } catch (RepositoryException e) {
663 throw new SlcException("Cannot get value for " + element, e);
664 }
665 }
666
667 @Override
668 protected void setValue(Object element, Object value) {
669 try {
670 Node specAttrNode = (Node) element;
671 if (specAttrNode
672 .isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) {
673 String type = specAttrNode.getProperty(SLC_TYPE)
674 .getString();
675 SlcJcrUtils.setPrimitiveAsProperty(specAttrNode, SLC_VALUE,
676 type, value);
677 valuesViewer.refresh();
678 formPart.markDirty();
679 }
680 } catch (RepositoryException e) {
681 throw new SlcException("Cannot get celle editor", e);
682 }
683 }
684
685 }
686 }