From: Mathieu Baudier Date: Thu, 21 Apr 2011 11:25:11 +0000 (+0000) Subject: Process builder display X-Git-Tag: argeo-slc-2.1.7~967 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=cb9c3adf8b024d64da158fbc76f8874629f377ed;p=gpl%2Fargeo-slc.git Process builder display ASSIGNED - bug 17: Generalize agent management and registration beyond JMS https://bugzilla.argeo.org/show_bug.cgi?id=17 git-svn-id: https://svn.argeo.org/slc/trunk@4469 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessBuilderPage.java b/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessBuilderPage.java index 9154714d8..8b8526055 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessBuilderPage.java +++ b/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessBuilderPage.java @@ -21,17 +21,24 @@ import org.argeo.eclipse.ui.jcr.AsyncUiEventListener; import org.argeo.jcr.JcrUtils; import org.argeo.slc.SlcException; import org.argeo.slc.client.ui.SlcImages; +import org.argeo.slc.core.execution.PrimitiveUtils; import org.argeo.slc.execution.ExecutionProcess; import org.argeo.slc.jcr.SlcJcrUtils; import org.argeo.slc.jcr.SlcNames; import org.argeo.slc.jcr.SlcTypes; -import org.argeo.slc.process.RealizedFlow; +import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerDropAdapter; @@ -52,6 +59,8 @@ import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.AbstractFormPart; import org.eclipse.ui.forms.IManagedForm; @@ -67,6 +76,7 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { private Node processNode; private TreeViewer flowsViewer; + private TableViewer valuesViewer; private Label statusLabel; private Button run; private Button remove; @@ -84,6 +94,8 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { protected void createFormContent(IManagedForm mf) { try { ScrolledForm form = mf.getForm(); + form.setExpandHorizontal(true); + form.setExpandVertical(true); form.setText("Process " + processNode.getName()); GridLayout mainLayout = new GridLayout(1, true); form.getBody().setLayout(mainLayout); @@ -109,6 +121,9 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { Event.PROPERTY_CHANGED, processNode.getPath(), true, null, null, false); + // add initial flows + addInitialFlows(); + } catch (RepositoryException e) { throw new ArgeoException("Cannot create form content", e); } @@ -176,22 +191,106 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { statusChanged(); } + protected void createBuilder(Composite parent) { + FormToolkit tk = getManagedForm().getToolkit(); + SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL); + sashForm.setSashWidth(4); + GridData sahFormGd = new GridData(SWT.FILL, SWT.FILL, true, true); + sahFormGd.widthHint = 400; + sashForm.setLayoutData(sahFormGd); + + Composite flowsComposite = tk.createComposite(sashForm); + flowsComposite.setLayout(new GridLayout(1, false)); + + flowsViewer = new TreeViewer(flowsComposite); + flowsViewer.getTree().setLayoutData( + new GridData(SWT.FILL, SWT.FILL, true, true)); + flowsViewer.setLabelProvider(new FlowsLabelProvider()); + flowsViewer.setContentProvider(new FlowsContentProvider()); + flowsViewer.addSelectionChangedListener(new FlowsSelectionListener()); + + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + flowsViewer.addDropSupport(operations, tt, new FlowsDropListener( + flowsViewer)); + + flowsViewer.setInput(getEditorSite()); + flowsViewer.setInput(processNode); + + Composite valuesComposite = tk.createComposite(sashForm); + valuesComposite.setLayout(new GridLayout(1, false)); + + valuesViewer = new TableViewer(valuesComposite); + GridData valuedGd = new GridData(SWT.FILL, SWT.FILL, true, true); + // valuedGd.widthHint = 200; + valuesViewer.getTable().setLayoutData(valuedGd); + valuesViewer.setContentProvider(new ValuesContentProvider()); + initializeValuesViewer(valuesViewer); + sashForm.setWeights(getWeights()); + valuesViewer.setInput(getEditorSite()); + } + + /** Creates the columns of the values viewer */ + protected void initializeValuesViewer(TableViewer viewer) { + String[] titles = { "Name", "Value" }; + int[] bounds = { 200, 100 }; + + for (int i = 0; i < titles.length; i++) { + TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setText(titles[i]); + column.getColumn().setWidth(bounds[i]); + column.getColumn().setResizable(true); + column.getColumn().setMoveable(true); + if (i == 0) { + column.setLabelProvider(new ColumnLabelProvider() { + public String getText(Object element) { + try { + Node specAttrNode = (Node) element; + return specAttrNode.getName(); + } catch (RepositoryException e) { + throw new SlcException("Cannot get value", e); + } + } + }); + } else if (i == 1) { + column.setLabelProvider(new ColumnLabelProvider() { + public String getText(Object element) { + Object obj = getAttributeSpecValue((Node) element); + return obj != null ? obj.toString() : ""; + } + }); + column.setEditingSupport(new ValuesEditingSupport(viewer)); + } + + } + Table table = viewer.getTable(); + table.setHeaderVisible(false); + table.setLinesVisible(true); + } + + protected int[] getWeights() { + return new int[] { 50, 50 }; + } + + /* + * CONTROLLERS + */ + /** Opens a new editor with a copy of this process */ protected void relaunch() { try { Node duplicatedNode = duplicateProcess(); - PlatformUI - .getWorkbench() - .getActiveWorkbenchWindow() - .getActivePage() - .openEditor( - new ProcessEditorInput(duplicatedNode.getPath()), - ProcessEditor.ID); + IWorkbenchPage activePage = PlatformUI.getWorkbench() + .getActiveWorkbenchWindow().getActivePage(); + activePage.openEditor( + new ProcessEditorInput(duplicatedNode.getPath()), + ProcessEditor.ID); getEditor().close(false); } catch (Exception e1) { throw new SlcException("Cannot relaunch " + processNode, e1); } } + /** Duplicates the process */ protected Node duplicateProcess() { try { Session session = processNode.getSession(); @@ -199,11 +298,11 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { String destPath = SlcJcrUtils.createExecutionProcessPath(uuid); Node newNode = JcrUtils.mkdirs(session, destPath, SLC_PROCESS); JcrUtils.copy(processNode, newNode); -// session.getWorkspace().copy(processNode.getPath(), destPath); -// Node newNode = session.getNode(destPath); + // session.getWorkspace().copy(processNode.getPath(), destPath); + // Node newNode = session.getNode(destPath); // make sure that we kept the mixins -// newNode.addMixin(NodeType.MIX_CREATED); -// newNode.addMixin(NodeType.MIX_LAST_MODIFIED); + // newNode.addMixin(NodeType.MIX_CREATED); + // newNode.addMixin(NodeType.MIX_LAST_MODIFIED); newNode.setProperty(SLC_UUID, uuid); newNode.setProperty(SLC_STATUS, ExecutionProcess.INITIALIZED); session.save(); @@ -213,15 +312,7 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { } } - protected String getProcessStatus() { - try { - return processNode.getProperty(SLC_STATUS).getString(); - } catch (RepositoryException e) { - throw new SlcException("Cannot retrieve status for " + processNode, - e); - } - } - + /** Reflects a status change */ protected void statusChanged() { String status = getProcessStatus(); statusLabel.setText(status); @@ -238,61 +329,48 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { } } - /** Optimization so that we don't call the node each time */ - protected Boolean isEditable(String status) { - return status.equals(ExecutionProcess.NEW) - || status.equals(ExecutionProcess.INITIALIZED); - } - - protected Boolean isFinished(String status) { - return status.equals(ExecutionProcess.COMPLETED) - || status.equals(ExecutionProcess.ERROR); - } - - protected void createBuilder(Composite parent) { - FormToolkit tk = getManagedForm().getToolkit(); - SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL); - sashForm.setSashWidth(4); - sashForm.setLayout(new FillLayout()); - sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Composite top = tk.createComposite(sashForm); - GridLayout gl = new GridLayout(1, false); - top.setLayout(gl); - - flowsViewer = new TreeViewer(top); - flowsViewer.getTree().setLayoutData( - new GridData(SWT.FILL, SWT.FILL, true, true)); - flowsViewer.setLabelProvider(new ViewLabelProvider()); - flowsViewer.setContentProvider(new ViewContentProvider()); - flowsViewer.addSelectionChangedListener(new SelectionChangedListener()); - - int operations = DND.DROP_COPY | DND.DROP_MOVE; - Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; - flowsViewer.addDropSupport(operations, tt, new ViewDropListener( - flowsViewer)); - - flowsViewer.setInput(getEditorSite()); - flowsViewer.setInput(processNode); - - Composite bottom = tk.createComposite(sashForm); - bottom.setLayout(new GridLayout(1, false)); - sashForm.setWeights(getWeights()); - } - - protected int[] getWeights() { - return new int[] { 70, 30 }; + /** Adds initial flows from the editor input if any */ + protected void addInitialFlows() { + for (String path : ((ProcessEditorInput) getEditorInput()) + .getInitialFlowPaths()) { + addFlow(path); + } } - /* - * CONTROLLERS + /** + * Adds a new flow. + * + * @param path + * the path of the flow */ - protected void addFlow(String path) { + public void addFlow(String path) { try { - Node flowNode = processNode.getNode(SLC_FLOW).addNode(SLC_FLOW); - flowNode.addMixin(SLC_REALIZED_FLOW); - Node address = flowNode.addNode(SLC_ADDRESS, NodeType.NT_ADDRESS); + Node flowNode = processNode.getSession().getNode(path); + Node realizedFlowNode = processNode.getNode(SLC_FLOW).addNode( + SLC_FLOW); + realizedFlowNode.addMixin(SLC_REALIZED_FLOW); + Node address = realizedFlowNode.addNode(SLC_ADDRESS, + NodeType.NT_ADDRESS); address.setProperty(Property.JCR_PATH, path); + + // copy spec attributes + Node specAttrsBase; + if (flowNode.hasProperty(SLC_SPEC)) + specAttrsBase = flowNode.getProperty(SLC_SPEC).getNode(); + else + specAttrsBase = flowNode; + + specAttrs: for (NodeIterator nit = specAttrsBase.getNodes(); nit + .hasNext();) { + Node specAttrNode = nit.nextNode(); + if (!specAttrNode + .isNodeType(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE)) + continue specAttrs; + Node realizedAttrNode = realizedFlowNode.addNode(specAttrNode + .getName()); + JcrUtils.copy(specAttrNode, realizedAttrNode); + } + flowsViewer.refresh(); formPart.markDirty(); } catch (RepositoryException e) { @@ -338,20 +416,63 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { formPart.commit(onSave); } + /* + * STATE + */ + protected String getProcessStatus() { + try { + return processNode.getProperty(SLC_STATUS).getString(); + } catch (RepositoryException e) { + throw new SlcException("Cannot retrieve status for " + processNode, + e); + } + } + + /** Optimization so that we don't call the node each time */ + protected Boolean isEditable(String status) { + return status.equals(ExecutionProcess.NEW) + || status.equals(ExecutionProcess.INITIALIZED); + } + + protected Boolean isFinished(String status) { + return status.equals(ExecutionProcess.COMPLETED) + || status.equals(ExecutionProcess.ERROR); + } + + /* + * LIFECYCLE + */ @Override public void dispose() { JcrUtils.unregisterQuietly(processNode, statusObserver); super.dispose(); } - // Specific Providers for the current view. - protected class ViewContentProvider implements ITreeContentProvider { - public void inputChanged(Viewer arg0, Object arg1, Object arg2) { + /* + * UTILITIES + */ + protected static Object getAttributeSpecValue(Node specAttrNode) { + try { + if (specAttrNode.isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) { + if (!specAttrNode.hasProperty(SLC_VALUE)) + return null; + String type = specAttrNode.getProperty(SLC_TYPE).getString(); + // TODO optimize based on data type? + Object value = PrimitiveUtils.convert(type, specAttrNode + .getProperty(SLC_VALUE).getString()); + return value; + } + return null; + } catch (RepositoryException e) { + throw new SlcException("Cannot get value", e); } - public void dispose() { - } + } + /* + * FLOWS SUBCLASSES + */ + static class FlowsContentProvider implements ITreeContentProvider { public Object[] getElements(Object obj) { if (!(obj instanceof Node)) return new Object[0]; @@ -368,7 +489,14 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { } } + public void inputChanged(Viewer arg0, Object arg1, Object arg2) { + } + + public void dispose() { + } + public Object[] getChildren(Object parentElement) { + // no children for the time being return null; } @@ -382,7 +510,7 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { } - protected class ViewLabelProvider extends ColumnLabelProvider { + static class FlowsLabelProvider extends ColumnLabelProvider { @Override public String getText(Object element) { @@ -417,26 +545,22 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { } - // Parameter view is updated each time a new line is selected - class SelectionChangedListener implements ISelectionChangedListener { + /** Parameter view is updated each time a new line is selected */ + class FlowsSelectionListener implements ISelectionChangedListener { public void selectionChanged(SelectionChangedEvent evt) { - - IStructuredSelection curSelection = (IStructuredSelection) evt - .getSelection(); - Object obj = curSelection.getFirstElement(); - - if (obj instanceof RealizedFlow) { - // RealizedFlow rf = (RealizedFlow) obj; - // curSelectedRow = realizedFlows.indexOf(rf); - // refreshParameterview(); - // setFocus(); + if (evt.getSelection().isEmpty()) { + valuesViewer.setInput(getEditorSite()); + return; } + Node realizedFlowNode = (Node) ((IStructuredSelection) evt + .getSelection()).getFirstElement(); + valuesViewer.setInput(realizedFlowNode); } } - protected class ViewDropListener extends ViewerDropAdapter { + class FlowsDropListener extends ViewerDropAdapter { - public ViewDropListener(Viewer viewer) { + public FlowsDropListener(Viewer viewer) { super(viewer); } @@ -453,4 +577,93 @@ public class ProcessBuilderPage extends FormPage implements SlcNames, SlcTypes { return isEditable(getProcessStatus()); } } + + /* + * VALUES SUBCLASSES + */ + static class ValuesContentProvider implements IStructuredContentProvider { + + public Object[] getElements(Object inputElement) { + if (!(inputElement instanceof Node)) + return new Object[0]; + + try { + Node realizedFlowNode = (Node) inputElement; + List specAttributes = new ArrayList(); + specAttrs: for (NodeIterator nit = realizedFlowNode.getNodes(); nit + .hasNext();) { + Node specAttrNode = nit.nextNode(); + if (!specAttrNode + .isNodeType(SlcTypes.SLC_EXECUTION_SPEC_ATTRIBUTE)) + continue specAttrs; + specAttributes.add(specAttrNode); + } + return specAttributes.toArray(); + } catch (RepositoryException e) { + throw new SlcException("Cannot get elements", e); + } + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + + static class ValuesEditingSupport extends EditingSupport { + private final TableViewer tableViewer; + + public ValuesEditingSupport(ColumnViewer viewer) { + super(viewer); + tableViewer = (TableViewer) viewer; + } + + @Override + protected CellEditor getCellEditor(Object element) { + try { + Node specAttrNode = (Node) element; + if (specAttrNode + .isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) + return new TextCellEditor(tableViewer.getTable()); + return null; + } catch (RepositoryException e) { + throw new SlcException("Cannot get celle editor", e); + } + } + + @Override + protected boolean canEdit(Object element) { + try { + Node specAttrNode = (Node) element; + return !(specAttrNode.getProperty(SLC_IS_IMMUTABLE) + .getBoolean() || specAttrNode.getProperty( + SLC_IS_CONSTANT).getBoolean()); + } catch (RepositoryException e) { + throw new SlcException("Cannot check canEdit", e); + } + } + + @Override + protected Object getValue(Object element) { + return getAttributeSpecValue((Node) element); + } + + @Override + protected void setValue(Object element, Object value) { + try { + Node specAttrNode = (Node) element; + if (specAttrNode + .isNodeType(SlcTypes.SLC_PRIMITIVE_SPEC_ATTRIBUTE)) { + String type = specAttrNode.getProperty(SLC_TYPE) + .getString(); + SlcJcrUtils.setPrimitiveAsProperty(specAttrNode, SLC_VALUE, + type, value); + } + } catch (RepositoryException e) { + throw new SlcException("Cannot get celle editor", e); + } + } + + } } diff --git a/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessEditor.java b/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessEditor.java index 78282df3c..59a0b01fe 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessEditor.java +++ b/eclipse/plugins/org.argeo.slc.client.ui/src/main/java/org/argeo/slc/client/ui/editors/ProcessEditor.java @@ -64,15 +64,6 @@ public class ProcessEditor extends FormEditor implements SlcTypes, SlcNames { processNode.setProperty(SLC_STATUS, ExecutionProcess.NEW); Node processFlow = processNode.addNode(SLC_FLOW); processFlow.addMixin(SLC_REALIZED_FLOW); - - // add initial flows - for (String path : pei.getInitialFlowPaths()) { - Node realizedFlow = processFlow.addNode(SLC_FLOW); - realizedFlow.addMixin(SLC_REALIZED_FLOW); - Node address = realizedFlow.addNode(SLC_ADDRESS, - NodeType.NT_ADDRESS); - address.setProperty(Property.JCR_PATH, path); - } return processNode; } diff --git a/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcJcrUtils.java b/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcJcrUtils.java index e4d5d6e6e..11a5ca289 100644 --- a/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcJcrUtils.java +++ b/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcJcrUtils.java @@ -75,6 +75,12 @@ public class SlcJcrUtils { PrimitiveAccessor primitiveAccessor) { String type = primitiveAccessor.getType(); Object value = primitiveAccessor.getValue(); + setPrimitiveAsProperty(node, propertyName, type, value); + } + + /** Map a primitive value to JCR ptoperty value. */ + public static void setPrimitiveAsProperty(Node node, String propertyName, + String type, Object value) { if (value == null) return; if (value instanceof CharSequence) @@ -97,7 +103,7 @@ public class SlcJcrUtils { else throw new SlcException("Unsupported type " + type); } catch (RepositoryException e) { - throw new SlcException("Cannot set primitive " + primitiveAccessor + throw new SlcException("Cannot set primitive of " + type + " as property " + propertyName + " on " + node, e); } } diff --git a/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcNames.java b/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcNames.java index 7d906c036..fa2704150 100644 --- a/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcNames.java +++ b/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/SlcNames.java @@ -11,6 +11,7 @@ public interface SlcNames { public final static String SLC_VALUE = "slc:value"; public final static String SLC_ADDRESS = "slc:address"; + public final static String SLC_SPEC = "slc:spec"; public final static String SLC_EXECUTION_SPECS = "slc:executionSpecs"; public final static String SLC_FLOW = "slc:flow"; diff --git a/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/execution/JcrExecutionModulesListener.java b/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/execution/JcrExecutionModulesListener.java index 27d7b6b3b..037161c8b 100644 --- a/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/execution/JcrExecutionModulesListener.java +++ b/runtime/org.argeo.slc.support.jcr/src/main/java/org/argeo/slc/jcr/execution/JcrExecutionModulesListener.java @@ -177,6 +177,7 @@ public class JcrExecutionModulesListener implements ExecutionModulesListener, executionSpecNode.setProperty(Property.JCR_DESCRIPTION, executionSpec.getDescription()); mapExecutionSpec(executionSpecNode, executionSpec); + flowNode.setProperty(SLC_SPEC, executionSpecNode); } else { mapExecutionSpec(flowNode, executionSpec); } diff --git a/runtime/org.argeo.slc.support.jcr/src/main/resources/org/argeo/slc/jcr/slc.cnd b/runtime/org.argeo.slc.support.jcr/src/main/resources/org/argeo/slc/jcr/slc.cnd index 3bdf51ce9..cb5d81256 100644 --- a/runtime/org.argeo.slc.support.jcr/src/main/resources/org/argeo/slc/jcr/slc.cnd +++ b/runtime/org.argeo.slc.support.jcr/src/main/resources/org/argeo/slc/jcr/slc.cnd @@ -47,7 +47,7 @@ mixin [slc:executionFlow] > nt:unstructured, mix:title - slc:name (STRING) m // if the execution spec is a referenceable node -- slc:executionSpec (REFERENCE) +- slc:spec (REFERENCE) // if the execution spec is internal (without name) + * (slc:executionSpecAttribute) *