You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2018/07/27 10:04:02 UTC

[37/53] [partial] brooklyn-ui git commit: Update Brooklyn codebase to build the newly donated UI

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/47b46402/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js b/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
deleted file mode 100755
index da3e090..0000000
--- a/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
+++ /dev/null
@@ -1,1021 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import * as d3 from 'd3';
-import {PREDICATE_MEMBERSPEC} from './model/entity.model';
-import addIcon from '../../img/icon-add.svg';
-import {ISSUE_LEVEL} from './model/issue.model';
-
-export function D3Blueprint(container) {
-    let _svg = d3.select(container).append('svg').attr('class', 'blueprint-canvas');
-    let _mirror = _svg.append('path').style('display', 'none');
-    let _zoomGroup = _svg.append('g').attr('class', 'zoom-group');
-    let _parentGroup = _zoomGroup.append('g').attr('class', 'parent-group');
-    let _linkGroup = _parentGroup.append('g').attr('class', 'link-group');
-    let _relationGroup = _parentGroup.append('g').attr('class', 'relation-group');
-    let _specNodeGroup = _parentGroup.append('g').attr('class', 'spec-node-group');
-    let _dropZoneGroup = _parentGroup.append('g').attr('class', 'dropzone-group');
-    let _ghostNodeGroup = _parentGroup.append('g').attr('class', 'ghost-node-group');
-    let _nodeGroup = _parentGroup.append('g').attr('class', 'node-group');
-    let _cloneGroup = _parentGroup.append('g').attr('class', 'clone-group');
-
-    let _dragState = {
-        dragInProgress: false,
-        dragStarted: false,
-        clone: null,
-        cloneX: 0,
-        cloneY: 0,
-    };
-
-    const _configHolder = {
-        nodes: {
-            root: {
-                rect: {
-                    class: 'node-root',
-                    x: -125,
-                    y: -50,
-                    width: 250,
-                    height: 100,
-                    rx: 50,
-                    ry: 50,
-                },
-                text: {
-                    class: 'node-name',
-                    width: 250,
-                    height: 100
-                },
-                maxNameLength: 18
-            },
-            child: {
-                circle: {
-                    r: 50,
-                    class: (d)=>(`node-cluster node-cluster-${d}`)
-                },
-                image: {
-                    class: 'node-icon',
-                    width: 64,
-                    height: 64,
-                    x: -32,
-                    y: -32,
-                    opacity: 0
-                }
-            },
-            location: {
-                rect: {
-                    x: -50,
-                    y: -110,
-                    width: 100,
-                    height: 50
-                },
-                image: {
-                    x: -50,
-                    y: -110,
-                    width: 100,
-                    height: 50,
-                    opacity: 0
-                }
-            },
-            dropzonePrev: {
-                circle: {
-                    cx: -150,
-                    r: 30,
-                    class: 'dropzone dropzone-prev'
-                },
-            },
-            dropzoneNext: {
-                circle: {
-                    cx: 150,
-                    r: 30,
-                    class: 'dropzone dropzone-next'
-                }
-            },
-            adjunct: {
-                rect: {
-                    id: (d)=>(`entity-${d._id}`),
-                    class: 'node-adjunct adjunct entity',
-                    width: 20,
-                    height: 20,
-                    transform: 'scale(0)'
-                }
-            },
-            memberspec: {
-                circle: {
-                    r: 35,
-                    cx: 0,
-                    cy: 170,
-                    class: 'node-spec-entity',
-                    'transform-origin': 0
-                },
-                image: {
-                    x: -20,
-                    y: 150,
-                    width: 40,
-                    height: 40,
-                    opacity: 0,
-                    class: 'node-spec-image',
-                    'transform-origin': 0
-                }
-            },
-            buttongroup: {
-                line: {
-                    class: 'link',
-                    x1: 0,
-                    x2: 0,
-                    y1: (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.height / 2 : _configHolder.nodes.child.circle.r),
-                    y2: (d)=>((isRootNode(d) ? _configHolder.nodes.root.rect.height / 2 : _configHolder.nodes.child.circle.r) + 30),
-                },
-                circle: {
-                    class: 'connector',
-                    r: 6,
-                    cy: (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.height / 2 : _configHolder.nodes.child.circle.r),
-                }
-            },
-            buttonAdd: {
-                circle: {
-                    r: 20,
-                    cy: 100
-                },
-                image: {
-                    width: 50,
-                    height: 50,
-                    x: -25,
-                    y: 75,
-                    'xlink:href': addIcon
-                }
-            }
-        },
-        transition: 300,
-        grid: {
-            itemPerCol: 3,
-            gutter: 15
-        },
-    };
-    let _d3DataHolder = {
-        nodes: [],
-        ghostNodes: [],
-        orphans: [],
-        links: [],
-        relationships: [],
-    };
-
-    let zoom = d3.zoom().scaleExtent([0.1, 1]).on('zoom', onSvgZoom);
-    _svg
-        .attr('preserveAspectRatio', 'xMinYMin meet')
-        .attr('viewBox', () => {
-            return `0 0 ${parseInt(_svg.style('width'))} ${parseInt(_svg.style('height'))}`;
-        })
-        .on('click', onSvgClick)
-        .on('dragover', onSvgDragOver)
-        .on('dragleave', onSvgDragLeave)
-        .call(zoom);
-
-    let pattern = _svg.append('pattern')
-        .attr('id', 'fill-has-issues')
-        .attr('width', 4)
-        .attr('height', 4)
-        .attr('patternUnits', 'userSpaceOnUse');
-    pattern.append('rect')
-        .attr('width', 4)
-        .attr('height', 4);
-    pattern.append('path')
-        .attr('d', 'M1 3h1v1H1V3zm2-2h1v1H3V1z');
-
-    let defs = _svg.append('defs');
-    let arrowhead = defs.append('marker')
-        .attr('id', 'arrowhead')
-        .attr('orient', 'auto')
-        .attr('markerWidth', 10)
-        .attr('markerHeight', 20)
-        .attr('markerUnits', 'userSpaceOnUse')
-        .attr('refX', 0)
-        .attr('refY', 10);
-    arrowhead.append('path')
-        .attr('d', 'M0,0 V20 L10,10 Z')
-        .attr('class', 'arrowhead');
-    let arrowheadHighlight = defs.append('marker')
-        .attr('id', 'arrowhead-highlight')
-        .attr('orient', 'auto')
-        .attr('markerWidth', 10)
-        .attr('markerHeight', 20)
-        .attr('markerUnits', 'userSpaceOnUse')
-        .attr('refX', 0)
-        .attr('refY', 10);
-    arrowheadHighlight.append('path')
-        .attr('d', 'M0,0 V20 L10,10 Z')
-        .attr('class', 'arrowhead-highlight');
-
-    /*****************************
-     ** EVENT HANDLERS :: START **
-     *****************************/
-
-    /**
-     * Handles translation and scaling of the zoom group
-     */
-    function onSvgZoom() {
-        _zoomGroup.attr('transform', d3.event.transform);
-    }
-
-    /**
-     * Fires a custom event "click-svg" when the use clicks anywhere on the canvas, except nodes.
-     */
-    function onSvgClick() {
-        if (d3.event.defaultPrevented) return;
-        let event = new CustomEvent('click-svg', {});
-        container.dispatchEvent(event);
-    }
-
-    /**
-     * Applies the "is-dragging" class to the canvas when a drag is initiated.
-     */
-    function onSvgDragOver() {
-        _svg.classed('is-dragging', true);
-
-        if (d3.event.dataTransfer && d3.event.dataTransfer.types.indexOf('entity') === -1) {
-            _dropZoneGroup.selectAll('.dropzone-prev').classed('hidden', true);
-            _dropZoneGroup.selectAll('.dropzone-next').classed('hidden', true);
-        }
-    }
-
-    /**
-     * Removes the "is-dragging" class from the canvas when a drag is finished.
-     */
-    function onSvgDragLeave() {
-        _svg.classed('is-dragging', false);
-
-        if (d3.event.dataTransfer && d3.event.dataTransfer.types.indexOf('entity') === -1) {
-            _dropZoneGroup.selectAll('.dropzone-prev').classed('hidden', false);
-            _dropZoneGroup.selectAll('.dropzone-next').classed('hidden', false);
-        }
-    }
-
-    /**
-     * Mouse Enter Event Handler
-     *
-     * @param {object} node The graph node the mouse is over
-     */
-    function onGhostOver(node) {
-        d3.select(`#ghost-node-${node.data._id} g.buttons`).classed('active', true);
-        // show whole group not just buttons
-    }
-
-    /**
-     * Mouse Leave Event Handler
-     *
-     * @param {object} node The graph node the mouse just left
-     */
-    function onGhostLeave(node) {
-        d3.select(`#ghost-node-${node.data._id} g.buttons`).classed('active', false);
-    }
-
-    /**
-     * Fires a custom event "click-entity" when a graph node is clicked.
-     *
-     * @param {object} node The node for the clicked entity
-     */
-    function onEntityClick(node) {
-        if (d3.event.defaultPrevented) return;
-        d3.event.stopPropagation();
-        let event = new CustomEvent('click-entity', {
-            detail: {
-                entity: node.data || node,
-            }
-        });
-        container.dispatchEvent(event);
-    }
-
-    /**
-     * Fires a custom event "click-add-child" when the plus button is clicked.
-     *
-     * @param {object} node The node for the entity to add a child to
-     */
-    function onAddChildClick(node) {
-        d3.event.stopPropagation();
-        let event = new CustomEvent('click-add-child', {
-            detail: {
-                entity: node.data,
-            }
-        });
-        container.dispatchEvent(event);
-    }
-
-    /**
-     * Handles the start of a drag operation. Note that this callback is to be used with the internal D3 drag feature.
-     *
-     * @param node The node for the dragged entity
-     */
-    function onDragStart(node) {
-        if (_dragState.clone) {
-            _dragState.clone.remove();
-            _dragState.clone = null;
-        }
-        if (node.depth) { // exclude the root element
-            onSvgDragOver();
-            hideInvalidDropzones(node);
-
-            d3.event.sourceEvent.preventDefault(); // disable browser text selection
-            d3.event.sourceEvent.stopPropagation();
-            _dragState.dragInProgress = true;
-            // at this point, we still don't know if this will be a click or a real drag
-            // so we defer the visual effects to the first 'dragging' event
-            _dragState.dragStarted = true;
-            let entityId = node.data._id;
-            let mouseCoords = d3.mouse(_nodeGroup.select(`#entity-${node.data._id}`).node());
-            _dragState.cloneX = node.x + _configHolder.nodes.child.circle.r + mouseCoords[0];
-            _dragState.cloneY = node.y + _configHolder.nodes.child.circle.r + mouseCoords[1];
-            _dragState.clone = _cloneGroup.append('use')
-                .attr('xlink:href', function(d) {
-                    return `#entity-${entityId}` } )
-                .attr('opacity', 0)
-                .attr('transform', (d)=>(`translate(${_dragState.cloneX}, ${_dragState.cloneY})`));
-        }
-    }
-
-    /**
-     * Handles the dragging operation. Note that this callback is to be used with the internal D3 drag feature.
-     *
-     * @param node The node for the dragged entity
-     */
-    function onDrag(node) {
-        if (_dragState.dragInProgress) {
-            if (_dragState.dragStarted) {
-                // deferred initialization of visual effects
-                _dragState.dragStarted = false;
-                hideRelationships(node);
-                _dragState.clone.attr('opacity', 0.5);
-            }
-            if (_dragState.clone) {
-                _dragState.cloneX += d3.event.dx;
-                _dragState.cloneY += d3.event.dy;
-                _dragState.clone.attr('transform', (d)=>(`translate(${_dragState.cloneX}, ${_dragState.cloneY})`));
-            }
-        }
-    }
-
-    /**
-     * Fires a custom event "move-entity" when an graph node has been dragged and dropped in a valid dropzone.
-     * Note that this callback is to be used with the internal D3 drag feature.
-     *
-     * @param node The node for the dragged entity
-     */
-    function onDragEnd(node) {
-        if (_dragState.dragInProgress) {
-            _dragState.dragInProgress = false;
-            // Firefox support (target)
-            let dropZone = d3.event.sourceEvent.toElement ? d3.event.sourceEvent.toElement : d3.event.sourceEvent.target;
-            if (['node-root', 'node-name', 'node-icon', 'node-cluster'].some(className => dropZone.classList.contains(className))) {
-                dropZone = dropZone.parentElement;
-            }
-            if (dropZone && dropZone.classList.contains('dropzone')) {
-                let parentId = dropZone.getAttribute('parentId');
-                let dropzoneId = dropZone.classList.contains('dropzone-next') || dropZone.classList.contains('dropzone-prev')
-                        ? dropZone.id
-                        : `dropzone-self-${parentId}`;
-                onDragLeave(node, dropzoneId);
-
-                if (node.data._id !== parentId) {
-                    let event = new CustomEvent('move-entity', {
-                        detail: {
-                            nodeId: node.data._id,
-                            parentId: parentId,
-                            targetIndex: dropZone.getAttribute('targetIndex'),
-                        },
-                    });
-                    container.dispatchEvent(event);
-                }
-            } else {
-                setTimeout(() => {
-                    showRelationships();
-                    showDropzones();
-                }, _configHolder.transition);
-            }
-        }
-        if (_dragState.clone) {
-            _dragState.clone.remove();
-            _dragState.clone = null;
-        }
-
-        onSvgDragLeave();
-    }
-
-    /**
-     * Applies the class "active" to the currently hovered dropzone, or the dropzone with the optional given ID.
-     *
-     * @param {*} node the graph node the event refers to
-     * @param {String} id Optional Id of the dropzone
-     */
-    function onDragOver(node, id) {
-        if (_dragState.dragInProgress || (d3.event && d3.event.type === 'dragover')) {
-            if (!id) {
-                id = (d3.event.sourceEvent ? d3.event.sourceEvent : d3.event).target.id;
-            }
-
-            if (id) {
-                _dropZoneGroup.select(`#${id}`).classed('active', true)
-            }
-        } else {
-            onGhostOver(node);
-        }
-    }
-
-    /**
-     * Removes the class "active" from the previously hovered dropzone, or the dropzone with the optional given ID.
-     *
-     * @param {*} node the graph node the event refers to
-     * @param {String} id Optional Id of the dropzone
-     */
-    function onDragLeave(node, id) {
-        if (_dragState.dragInProgress  ||
-                (d3.event && (['end','drop','dragleave'].includes(d3.event.type)))) {
-            if (!id) {
-                id = (d3.event.sourceEvent ? d3.event.sourceEvent : d3.event).target.id;
-            }
-
-            if (id) {
-                _dropZoneGroup.select(`#${id}`).classed('active', false)
-            }
-        } else {
-            onGhostLeave(node);
-        }
-    }
-
-    /**
-     * Fires a custom event "drop-node" when an external node has been dragged and dropped in a valid dropzone.
-     * Note that this callback is to be used with the external drag feature, i.e. HTML5
-     *
-     * @param {String} id Optional Id of the dropzone
-     */
-    function onExternalDrop(node, id) {
-        let dropZone = d3.event.toElement ? d3.event.toElement : d3.event.target;
-        if (['node-root', 'node-name', 'node-icon', 'node-cluster'].some(className => dropZone.classList.contains(className))) {
-            dropZone = dropZone.parentElement;
-        }
-        if (dropZone && dropZone.classList.contains('dropzone')) {
-            onDragLeave(node, id);
-
-            let event = new CustomEvent('drop-external-node', {
-                detail: {
-                    parentId: dropZone.getAttribute('parentId'),
-                    targetIndex: dropZone.getAttribute('targetIndex'),
-                },
-            });
-            container.dispatchEvent(event);
-        }
-
-        onSvgDragLeave();
-    }
-
-    /***************************
-     ** EVENT HANDLERS :: END **
-     ***************************/
-
-    /**
-     * Update the graph data
-     *
-     * @param {object} blueprint The graph
-     */
-    function update(blueprint, relationships) {
-        let tree = d3.tree()
-            .nodeSize([_configHolder.nodes.child.circle.r * 6, _configHolder.nodes.child.circle.r * 6])
-            .separation((right, left)=> {
-                let maxColumnsBeforeExpand = 2;
-                let adjuncts = getImportantAdjuncts(left).length;
-                let currentCols = Math.floor(adjuncts / _configHolder.grid.itemPerCol) + (adjuncts > 0 && adjuncts % _configHolder.grid.itemPerCol !== 0 ? 1 : 0);
-                let additionalCol = currentCols > maxColumnsBeforeExpand ? currentCols - maxColumnsBeforeExpand : 0;
-
-                let colWidth = _configHolder.nodes.adjunct.rect.width + 15;
-
-                return 1 + (colWidth / (_configHolder.nodes.child.circle.r * 6)) * additionalCol;
-            });
-        let root = d3.hierarchy(blueprint);
-        tree(root);
-        _d3DataHolder.nodes = root.descendants();
-        _d3DataHolder.links = root.links();
-        _d3DataHolder.relationships = relationships;
-        return this;
-    }
-
-    /**
-     * Redraw the graph
-     */
-    function draw() {
-        drawLinks();
-        drawRelationships();
-        drawNodeGroup();
-        drawSpecNodeGroup();
-        drawGhostNode();
-        drawDropZoneGroup();
-        return this;
-    }
-
-    function drawNodeGroup() {
-        let nodeData = _nodeGroup.selectAll('g.node')
-            .data(_d3DataHolder.nodes, (d)=>(`node-${d.data._id}`));
-
-        // Draw group that contains all SVG element: node representation and location/policies/enricher indicators
-        // -----------------------------------------------------
-        let nodeGroup = nodeData
-            .enter()
-            .append('g')
-            .attr('id', (d)=>(`node-group-${d.data._id}`))
-            .attr('class', 'node')
-            .classed('node-root', isRootNode)
-            .classed('node-child', isChildNode)
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) scale(${isRootNode(d) ? 1 : 0})`))
-            .attr('opacity', (d)=> (isRootNode(d) ? 0 : 1));
-        nodeData.transition()
-            .duration(_configHolder.transition)
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) scale(1)`))
-            .attr('opacity', 1);
-        nodeData.exit()
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) scale(${isRootNode(d) ? 1 : 0})`))
-            .attr('opacity', (d)=> (isRootNode(d) ? 0 : 1))
-            .remove();
-
-        // Draw the node-entity group that will contain the node representation
-        // -----------------------------------------------------
-        let entity = nodeGroup.append('g')
-            .attr('class', 'node-entity entity')
-            .on('click', onEntityClick)
-            .on('mouseenter', onGhostOver)
-            .on('mouseleave', onGhostLeave)
-            .call(d3.drag()
-                .on('start', onDragStart)
-                .on('drag', onDrag)
-                .on('end', onDragEnd));
-        nodeData.select('g.node-entity')
-            .attr('id', (d)=>(`entity-${d.data._id}`))
-            .classed('clustered', (d)=>(d.data.isCluster()))
-            .classed('has-warnings', (d)=>(d.data.hasIssues() && d.data.issues.some(issue => issue.level === ISSUE_LEVEL.WARN)))
-            .classed('has-errors', (d)=>(d.data.hasIssues() && d.data.issues.some(issue => issue.level === ISSUE_LEVEL.ERROR)))
-            .classed('loading', (d)=>(d.data.miscData.get('loading')));
-
-        // Draw root node
-        appendElements(entity.filter(isRootNode), _configHolder.nodes.root);
-        nodeData.filter(isRootNode).select('.node-entity text')
-            .text(trimNodeText)
-            .transition()
-            .duration(_configHolder.transition)
-            .text(trimNodeText);
-        nodeData.filter(isChildNode).select('.node-entity image')
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('opacity', (d)=>(d.data.hasIcon() ? 1 : 0))
-            .attr('xlink:href', (d)=>(d.data.icon));
-
-        // Draw child nodes
-        appendElement(entity.filter(isChildNode).selectAll('circle').data([2, 1, 0]).enter(), 'circle', _configHolder.nodes.child.circle);
-        appendElement(entity.filter(isChildNode), 'image', _configHolder.nodes.child.image);
-
-        // Draw location
-        // -----------------------------------------------------
-        let location = nodeGroup.append('g')
-            .attr('class', 'node-location')
-            .classed('loading', (d)=>(d.data.miscData.get('loading')));
-        nodeData.select('g.node-location')
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('opacity', (d)=>(d.data.hasLocation() ? 1 : 0));
-        appendElements(location, _configHolder.nodes.location);
-        nodeData.select('g.node-location image')
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('opacity', (d)=>(d.data.miscData.get('locationIcon') ? 1 : 0))
-            .attr('xlink:href', (d)=>(d.data.miscData.get('locationIcon')));
-
-        // Draw important adjuncts (i.e policies/enrichers)
-        // -----------------------------------------------------
-        nodeGroup.append('g')
-            .attr('class', 'node-adjuncts');
-        let adjunctData = nodeData.select('g.node-adjuncts')
-            .selectAll('rect.adjunct')
-            .data((d)=>(getImportantAdjuncts(d)), (d)=>(`adjunct-${d._id}`));
-        adjunctData
-            .classed('has-warnings', (d)=>(d.hasIssues() && d.issues.some(issue => issue.level === ISSUE_LEVEL.WARN)))
-            .classed('has-errors', (d)=>(d.hasIssues() && d.issues.some(issue => issue.level === ISSUE_LEVEL.ERROR)))
-            .classed('loading', (d)=>(d.miscData.get('loading')))
-            .on('click', onEntityClick);
-        adjunctData.transition()
-            .duration(_configHolder.transition)
-            .attr('x', (d, i)=>(getGridX(d, i)))
-            .attr('y', (d, i)=>(getGridY(d, i)))
-            .attr('transform', 'scale(1)')
-            .attr('transform-origin', (d, i)=>(getGridItemCenter(d, i)));
-        adjunctData.exit()
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', 'scale(0)')
-            .remove();
-        appendElement(adjunctData.enter(), 'rect', _configHolder.nodes.adjunct.rect);
-    }
-
-    function drawLinks() {
-        let link = _linkGroup.selectAll('line.link')
-            .data(_d3DataHolder.links, (d)=>(d.source.data._id + '_to_' + d.target.data._id));
-
-        link.enter().insert('line')
-            .attr('class', 'link')
-            .attr('x1', (d)=>(d.source.x))
-            .attr('y1', (d)=>(d.source.y))
-            .attr('x2', (d)=>(d.source.x))
-            .attr('y2', (d)=>(d.source.y));
-        link.transition()
-            .duration(_configHolder.transition)
-            .attr('x1', (d)=>(d.source.x))
-            .attr('y1', (d)=>(d.source.y))
-            .attr('x2', (d)=>(d.target.x))
-            .attr('y2', (d)=>(d.target.y));
-        link.exit()
-            .transition()
-            .attr('opacity', 0)
-            .remove();
-    }
-
-    /**
-     * returns the D3 tree node for a given Entity
-     * @param {Entity} entity
-     * @return {*} a D3 tree node
-     */
-    function nodeForEntity(entity) {
-        let node = _d3DataHolder.nodes.find((d)=>(d.data._id === entity._id));
-        if (!node) {
-            throw new Error('Node for Entity ' + entity._id + ' not found');
-        }
-        return node;
-    }
-
-    function drawRelationships() {
-        showRelationships();
-
-        let relationData = _relationGroup.selectAll('.relation')
-            .data(_d3DataHolder.relationships, (d)=>(d.source._id + '_related_to_' + d.target._id));
-
-        relationData.enter().insert('path')
-            .attr('class', 'relation')
-            .attr('opacity', 0)
-            .attr('from', (d)=>(d.source._id))
-            .attr('to', (d)=>(d.target._id));
-        relationData.transition()
-            .duration(_configHolder.transition)
-            .attr('opacity', 1)
-            .attr('stroke', 'red')
-            .attr('d', function(d) {
-                let targetNode = nodeForEntity(d.target);
-                let sourceNode = nodeForEntity(d.source);
-                let dx = targetNode.x - sourceNode.x;
-                let dy = targetNode.y - sourceNode.y;
-                let dr = Math.sqrt(dx * dx + dy * dy);
-                let sweep = dx * dy > 0 ? 0 : 1;
-                _mirror.attr('d', `M ${sourceNode.x},${sourceNode.y} A ${dr},${dr} 0 0,${sweep} ${targetNode.x},${targetNode.y}`);
-
-                let m = _mirror._groups[0][0].getPointAtLength(_mirror._groups[0][0].getTotalLength() - _configHolder.nodes.child.circle.r - 20);
-
-                dx = m.x - sourceNode.x;
-                dy = m.y - sourceNode.y;
-                dr = Math.sqrt(dx * dx + dy * dy);
-
-                return `M ${sourceNode.x},${sourceNode.y} A ${dr},${dr} 0 0,${sweep} ${m.x},${m.y}`;
-            });
-        relationData.exit()
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('opacity', 0)
-            .remove();
-    }
-
-    function drawGhostNode() {
-        let ghostNodeData = _ghostNodeGroup.selectAll('g.ghost-node')
-            .data(_d3DataHolder.nodes, (d)=>(`ghost-node-${d.data._id}`));
-        let ghostNode = ghostNodeData
-            .enter()
-            .append('g')
-            .attr('id', (d)=>(`ghost-node-${d.data._id}`))
-            .attr('class', 'ghost-node')
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`))
-            .on('mouseenter', onGhostOver)
-            .on('mouseleave', onGhostLeave);
-        ghostNodeData
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
-        ghostNodeData.exit().remove();
-
-        ghostNode.append('rect')
-            .attr('class', 'ghost')
-            .attr('width', (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.width : _configHolder.nodes.child.circle.r * 2))
-            .attr('height', (d)=>((isRootNode(d) ? _configHolder.nodes.root.rect.height : _configHolder.nodes.child.circle.r * 2) + 80))
-            .attr('x', (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.x : -_configHolder.nodes.child.circle.r))
-            .attr('y', (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.y : -_configHolder.nodes.child.circle.r));
-
-        let buttonsGroup = ghostNode.append('g')
-            .attr('class', 'buttons');
-        appendElements(buttonsGroup, _configHolder.nodes.buttongroup);
-
-        let buttonAdd = buttonsGroup.append('g')
-            .attr('class', 'button button-add')
-            .on('click', onAddChildClick);
-        appendElements(buttonAdd, _configHolder.nodes.buttonAdd);
-    }
-
-    function drawDropZoneGroup() {
-        showDropzones();
-
-        let dropZoneData = _dropZoneGroup.selectAll('g.dropzone-group-node')
-            .data(_d3DataHolder.nodes, (d)=>(`dropzone-${d.data._id}`));
-
-        let dropZoneGroup = dropZoneData
-            .enter()
-            .append('g')
-            .attr('id', (d)=>(`dropzone-group-node-${d.data._id}`))
-            .attr('class', 'dropzone-group-node')
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
-        dropZoneData
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
-        dropZoneData.exit().remove();
-
-        appendElement(dropZoneGroup.filter(isRootNode), 'rect', Object.assign({},
-            _configHolder.nodes.root.rect,
-            // expand above by 7
-            {x: -132, y: -57, rx: 57, ry: 57, width: 264, height: 114, class: 'dropzone dropzone-self'}));
-        appendElement(dropZoneGroup.filter(isChildNode), 'circle', Object.assign({},
-            _configHolder.nodes.child.circle,
-            {transform: (d) => (`scale(${d.data.isCluster() ? 1.5 : 1.15})`), class: 'dropzone dropzone-self'}));
-        appendElements(dropZoneGroup.filter(isChildNode), _configHolder.nodes.dropzonePrev);
-        appendElements(dropZoneGroup.filter(isChildNode), _configHolder.nodes.dropzoneNext);
-
-        dropZoneData.select('.dropzone-self')
-            .attr('id', (d)=>(`dropzone-self-${d.data._id}`))
-            .attr('parentId', (d) => (d.data._id))
-            .attr('targetIndex', -1);
-        dropZoneData.select('.dropzone-prev')
-            .attr('id', (d)=>(`dropzone-prev-${d.data._id}`))
-            .attr('parentId', (d) => (d.data.parent ? d.data.parent._id : ''))
-            .attr('targetIndex', (d) => (d.data.parent ? d.data.parent.children.indexOf(d.data) : -1));
-        dropZoneData.select('.dropzone-next')
-            .attr('id', (d)=>(`dropzone-next-${d.data._id}`))
-            .attr('parentId', (d) => (d.data.parent ? d.data.parent._id : ''))
-            .attr('targetIndex', (d) => (d.data.parent ? d.data.parent.children.indexOf(d.data) + 1 : -1));
-
-        dropZoneData.selectAll('.dropzone')
-            // D3 drag
-            .on('mouseenter', (d) => onDragOver(d))
-            .on('mouseleave', (d) => onDragLeave(d))
-            // Palette drag
-            .on('dragover', (d) => {
-                // We prevent the default to mark this dropzone as valid. Not doing so means that the "drop" event won't be fired.
-                d3.event.preventDefault();
-                onDragOver(d);
-            })
-            .on('dragleave', (d) => onDragLeave(d))
-            .on('drop', (d) => onExternalDrop(d));
-
-        _nodeGroup.selectAll('.node-entity')
-            .classed('dropzone', true)
-            .attr('parentId', (d)=>(d.data._id))
-            .attr('targetIndex', -1)
-            // D3 drag
-            .on('mouseenter', (d) => (onDragOver(d, `dropzone-self-${d.data._id}`)))
-            .on('mouseleave', (d) => (onDragLeave(d, `dropzone-self-${d.data._id}`)))
-            // Palette drag
-            .on('dragover', (d) => {
-                // We prevent the default to mark this dropzone as valid. Not doing so means that the "drop" event won't be fired.
-                d3.event.preventDefault();
-                onDragOver(d, `dropzone-self-${d.data._id}`);
-            })
-            .on('dragleave', (d) => (onDragLeave(d, `dropzone-self-${d.data._id}`)))
-            .on('drop', (d) => onExternalDrop(d, `dropzone-self-${d.data._id}`));
-    }
-
-    function drawSpecNodeGroup() {
-        let specNodeData = _specNodeGroup.selectAll('g.spec-node')
-            .data(_d3DataHolder.nodes.filter((node)=>{
-                return !!node.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC);
-            }), (d)=>(`spec-node-${d.data._id}`));
-        let specNodeGroup = specNodeData
-            .enter()
-            .append('g')
-            .attr('id', (d)=>(`spec-node-${d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC)._id}`))
-            .attr('class', 'spec-node')
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
-        specNodeData.transition()
-            .duration(_configHolder.transition)
-            .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) rotate(${d.data.hasChildren() ? -45 : 0})`));
-        specNodeData.exit()
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('opacity', 0)
-            .remove();
-
-        specNodeGroup.append('polygon')
-            .attr('class', 'node-memberspec-link')
-            .attr('points', (d)=> {
-                let left = _configHolder.nodes.memberspec.circle.r * -1;
-                let right = _configHolder.nodes.memberspec.circle.r;
-                let bottom = _configHolder.nodes.memberspec.circle.cy;
-                return `0,0 ${right},${bottom} ${left},${bottom}`;
-            })
-            .attr('transform', 'scale(0)');
-        specNodeData.select('polygon')
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', 'scale(1)');
-
-        let specNode = specNodeGroup.append('g')
-            .attr('class', 'node-memberspec entity')
-            .attr('id', (d)=>(`entity-${d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC)._id}`))
-            .attr('transform-origin', `0 ${_configHolder.nodes.memberspec.circle.cy}`)
-            .attr('transform', 'scale(0)')
-            .on('click', (d)=>(onEntityClick({data: d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC)})));
-        specNodeData.select('.node-memberspec')
-            .classed('has-issues', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).hasIssues()))
-            .classed('loading', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).miscData.get('loading')));
-        specNodeData.select('.node-memberspec')
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', 'scale(1)');
-        appendElements(specNode, _configHolder.nodes.memberspec);
-        specNodeData.select('image')
-            .transition()
-            .duration(_configHolder.transition)
-            .attr('transform', (d)=>(`rotate(${d.data.hasChildren() ? 45 : 0})`))
-            .attr('transform-origin', 0)
-            .attr('opacity', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).hasIcon() ? 1 : 0))
-            .attr('xlink:href', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).icon));
-    }
-
-    function appendElements(node, definition) {
-        let elements = [];
-        Object.keys(definition).forEach((tag)=> {
-            let properties = definition[tag];
-            let element = appendElement(node, tag, properties);
-            elements.push(element);
-        });
-        return elements;
-    }
-
-    function appendElement(node, tag, properties) {
-        let element = node.append(tag);
-        Object.keys(properties).forEach((property)=> {
-            element.attr(property, properties[property]);
-        });
-        return element;
-    }
-
-    /**
-     * Calculate the X coordinate of a policies/enricher to place it on the grid
-     *
-     * @param d the current {entity}
-     * @param i the index
-     * @returns {number} The X coordinate within the grid
-     */
-    function getGridX(d, i) {
-        let nodeWidth = isRootNode(d.parent) ? _configHolder.nodes.root.rect.width : _configHolder.nodes.child.circle.r * 2;
-        let offset = (_configHolder.nodes.adjunct.rect.width + _configHolder.grid.gutter) * Math.floor(i / _configHolder.grid.itemPerCol);
-        if (d.parent.isCluster()) {
-            offset += 20;
-        }
-
-        return _configHolder.grid.gutter + (nodeWidth/2) + offset;
-    }
-
-    /**
-     * Calculate the Y coordinate of a policies/enricher to place it on the grid
-     *
-     * @param d the current {entity}
-     * @param i the index
-     * @returns {number} The Y coordinate within the grid
-     */
-    function getGridY(d, i) {
-        let nodeHeight = isRootNode(d.parent) ? _configHolder.nodes.root.rect.height : _configHolder.nodes.child.circle.r * 2;
-        let columnHeight = _configHolder.nodes.adjunct.rect.height * _configHolder.grid.itemPerCol + _configHolder.grid.gutter * (_configHolder.grid.itemPerCol - 1);
-        let offset = nodeHeight > columnHeight ? (nodeHeight - columnHeight) / 2 : 0;
-
-        return (_configHolder.nodes.adjunct.rect.height + _configHolder.grid.gutter) * (i%_configHolder.grid.itemPerCol) - (nodeHeight/2) + offset;
-    }
-
-    /**
-     * Calculate the center coordinates of a policies/enricher to place it on the grid
-     *
-     * @param d the current {entity}
-     * @param i the index
-     * @returns {number} The center coordinates within the grid
-     */
-    function getGridItemCenter(d, i) {
-        let centerX = getGridX(d, i) + _configHolder.nodes.adjunct.rect.width / 2;
-        let centerY = getGridY(d, i) + _configHolder.nodes.adjunct.rect.height / 2;
-        return `${centerX} ${centerY}`;
-    }
-
-    /**
-     * Center the graph in the view, considering palette
-     */
-    function center() {
-        let newX = window.innerWidth/2 + (window.innerWidth > 660 ? 220 : 0);
-        let newY = _configHolder.nodes.child.circle.r + (_configHolder.nodes.child.circle.r * 2);
-        zoom.translateBy(_svg, newX, newY);
-        return this;
-    }
-
-    function trimNodeText(d) {
-        if (!d.data.metadata.has('name') || d.data.metadata.get('name').length === 0) {
-            return 'New application';
-        } else {
-            let name = d.data.metadata.get('name');
-            return name.length > _configHolder.nodes.root.maxNameLength ? name.substring(0, _configHolder.nodes.root.maxNameLength) + '...' : name
-        }
-    }
-
-    function isRootNode(d) {
-        return d.depth === 0;
-    }
-
-    function isChildNode(d) {
-        return d.depth > 0;
-    }
-
-    function getImportantAdjuncts(d) {
-        let adjuncts = d.data.getPoliciesAsArray().concat(d.data.getEnrichersAsArray());
-        return adjuncts.filter((adjunct)=>(adjunct.miscData.has('important') && adjunct.miscData.get('important') === true));
-    }
-
-    function selectNode(id) {
-        _svg.selectAll('.entity.selected').classed('selected', false);
-        _svg.selectAll('.relation.highlight').classed('highlight', false);
-        _svg.select(`#entity-${id}`).classed('selected', true);
-        _svg.selectAll(`.relation[from='${id}']`).classed('highlight', true);
-        _svg.selectAll(`.relation[to='${id}']`).classed('highlight', true);
-        return this;
-    }
-
-    function unselectNode() {
-        _svg.selectAll('.entity.selected').classed('selected', false);
-        _svg.selectAll('.relation.highlight').classed('highlight', false);
-        return this;
-    }
-
-    /**
-     * Hide the relationships for the dragged entity and its descendants
-     * @param node the node for the dragged entity
-     */
-    function hideRelationships(node) {
-        _d3DataHolder.relationships
-            .filter(r => r.source.hasAncestor(node.data) || r.target.hasAncestor(node.data))
-            .forEach(r => {
-                _relationGroup.selectAll(`.relation[from='${r.source._id}'][to='${r.target._id}']`).classed('hidden', true);
-            });
-    }
-
-    /**
-     * Shows all relationships
-     */
-    function showRelationships() {
-        _relationGroup.selectAll('.relation').classed('hidden', false);
-    }
-
-    /**
-     * Hide the invalid dropzones for the dragged node
-     * @param node the node that is being dragged
-     */
-    function hideInvalidDropzones(node) {
-        _d3DataHolder.nodes
-            .filter(d => d.data.hasAncestor(node.data))
-            .forEach(d => {
-                _dropZoneGroup.selectAll(`#dropzone-group-node-${d.data._id} .dropzone`).classed('hidden', true);
-                _dropZoneGroup.selectAll(`.dropzone-prev[parentId='${d.data.parent._id}'][targetIndex='${d.data.parent.children.indexOf(d.data)+1}']`).classed('hidden', true);
-                _dropZoneGroup.selectAll(`.dropzone-next[parentId='${d.data.parent._id}'][targetIndex='${d.data.parent.children.indexOf(d.data)}']`).classed('hidden', true);
-            });
-    }
-
-    /**
-     * Shows all dropzones
-     */
-    function showDropzones() {
-        _dropZoneGroup.selectAll('.dropzone').classed('hidden', false);
-    }
-
-    return {
-        draw: draw,
-        update: update,
-        center: center,
-        select: selectNode,
-        unselect: unselectNode
-    };
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/47b46402/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js b/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js
deleted file mode 100644
index 7b78a0a..0000000
--- a/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js
+++ /dev/null
@@ -1,1023 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import {Entity} from './entity.model';
-
-const FUNCTION_PREFIX = '$brooklyn:';
-
-const TARGETS = [ 'self', 'parent', 'child', 'sibling', 'descendant', 'ancestor', 'root', 'scopeRoot', 'entity', 'component', ];
-
-const UTILITIES = [ 'literal', 'formatString', 'urlEncode', 'regexReplacement' ];
-
-export const FAMILY = {
-    CONSTANT: 'constant',
-    FUNCTION: 'function',
-    REFERENCE: 'reference',
-};
-
-export const KIND = {
-    UTILITY : {family: FAMILY.FUNCTION,  name: 'utility function'},
-    TARGET  : {family: FAMILY.FUNCTION,  name: 'target entity function'},
-    METHOD  : {family: FAMILY.FUNCTION,  name: 'method'},
-    ENTITY  : {family: FAMILY.REFERENCE, name: 'entity object'},
-    STRING  : {family: FAMILY.CONSTANT,  name: 'constant string'},
-    NUMBER  : {family: FAMILY.CONSTANT,  name: 'constant number'},
-    PORT    : {family: FAMILY.CONSTANT,  name: 'constant port'},
-};
-
-const ID = new WeakMap();
-const PREV = new WeakMap();
-const NEXT = new WeakMap();
-const PARENT = new WeakMap();
-const PARAMS = new WeakMap();
-const KINDS = new WeakMap();
-const REF = new WeakMap();
-const NAME = new WeakMap();
-const RELATIONSHIPS = new WeakMap();
-const ISSUES = new WeakMap();
-
-const numberRegex = /^[+-]?\d+(?:\.\d*)?|^[+-]?\.\d+/;
-
-const portRangeRegex = /[\d]+\+|[\d]+-[\d]+/;
-
-/**
- * A component of a Dsl expression.
- * This class can represent a constant (e.g., a string or number),
- * an Entity object, or a function call with parameters.
- * Dsl expression are composable, e.g. function parameters are also Dsl expressions.
- * Function calls can also be chained to other function calls.
- */
-export class Dsl {
-    /**
-     * @param kind
-     * @param {string} name
-     * @param {Entity} entity
-     */
-    constructor(kind = KIND.STRING, name, entity) {
-        ID.set(this, Math.random().toString(36).slice(2));
-        PARAMS.set(this, new Array());
-        KINDS.set(this, kind);
-        NAME.set(this, name === undefined ? ID.get(this).toString() : name.toString());
-        RELATIONSHIPS.set(this, new Array());
-        ISSUES.set(this, new Array());
-    }
-
-    /**
-     * The internal entity id
-     * @returns {string}
-     */
-    get _id() {
-        return ID.get(this);
-    }
-
-    /**
-     * Get {Dsl} name
-     * @returns {string}
-     */
-    get name() {
-        return NAME.get(this);
-    }
-
-    /**
-     * Set {Dsl} name
-     * @param {string} name
-     */
-    set name(name) {
-        if (name instanceof String || typeof name === 'string') {
-            NAME.set(this, name.toString());
-        } else {
-            throw new DslError('Cannot set name ... name must be a string: ' + typeof name);
-        }
-    }
-
-    /**
-     * Set Entity reference
-     * @param {Entity} entity
-     */
-    set ref(entity) {
-        if (entity instanceof Entity) {
-            REF.set(this, entity);
-            this.kind = KIND.ENTITY;
-        } else {
-            throw new DslError('Cannot set ref ... ref must be of type Entity');
-        }
-    }
-
-    /**
-     * Get Entity reference
-     * @return {Entity}
-     */
-    get ref() {
-        return REF.get(this);
-    }
-
-    /**
-     * Get {Dsl} parent
-     * @returns {Dsl}
-     */
-    get parent() {
-        return PARENT.get(this);
-    }
-
-    /**
-     * Set {Dsl} parent
-     * @param {Dsl} parent
-     */
-    set parent(parent) {
-        if (parent instanceof Dsl) {
-            if (PARENT.get(this) !== parent) {
-                PARENT.set(this, parent);
-            }
-        } else {
-            throw new DslError('Cannot add parent ... parent must be of type Dsl');
-        }
-    }
-
-    /**
-     * Get {Dsl} prev
-     * @returns {Dsl}
-     */
-    get prev() {
-        return PREV.get(this);
-    }
-
-    /**
-     * Set {Dsl} prev
-     * @param {Dsl} prev
-     */
-    set prev(prev) {
-        if (prev instanceof Dsl) {
-            if (PREV.get(this) !== prev) {
-                PREV.set(this, prev);
-            }
-        } else {
-            throw new DslError('Cannot set prev ... prev must be of type Dsl');
-        }
-    }
-
-    /**
-     * Get {Dsl} next
-     * @returns {Dsl}
-     */
-    get next() {
-        return NEXT.get(this);
-    }
-
-    /**
-     * Set {Dsl} next
-     * @param {Dsl} next
-     */
-    set next(next) {
-        if (next instanceof Dsl) {
-            if (NEXT.get(this) !== next) {
-                NEXT.set(this, next);
-            }
-        } else {
-            throw new DslError('Cannot set next ... next must be of type Dsl');
-        }
-    }
-
-    /**
-     * Get parameters
-     * @return {Array}
-     */
-    get params() {
-        return PARAMS.get(this);
-    }
-
-    /**
-     * Get kind
-     * @return
-     */
-    get kind() {
-        return KINDS.get(this);
-    }
-
-    /**
-     * Set kind
-     * @param kind
-     */
-    set kind(kind) {
-        if (Object.values(KIND).includes(kind)) {
-            KINDS.set(this, kind);
-        }
-        else {
-            throw new DslError('Cannot set kind ... not a valid KIND');
-        }
-    }
-
-    /**
-     * Get relationships
-     * @return {Array} an array of Entities
-     */
-    get relationships() {
-        return RELATIONSHIPS.get(this);
-    }
-
-    /**
-     * Set relationships
-     * @param {Array} relationships an array of Entities
-     */
-    set relationships(relationships) {
-        RELATIONSHIPS.set(this, relationships);
-    }
-
-    /**
-     * Get issues
-     * @return {Array}
-     */
-    get issues() {
-        return ISSUES.get(this);
-    }
-
-    /**
-     * Set issues
-     * @param {Array} issues
-     */
-    set issues(issues) {
-        ISSUES.set(this, issues);
-    }
-
-    /**
-     * Push param {Dsl}
-     * @param {Dsl} param
-     * @returns {Dsl}
-     */
-    param(param) {
-        if (param instanceof Dsl) {
-            if (this.kind.family !== FAMILY.FUNCTION) {
-                throw new DslError('Cannot push param to non-function... Dsl kind is: ' + this.kind.name);
-            }
-            PARAMS.get(this).push(param);
-            param.parent = this;
-            return this;
-        } else {
-            throw new DslError('Cannot push param ... param must be of type Dsl');
-        }
-    }
-
-    /**
-     * Pop param
-     * @param {string} id
-     * @returns {Dsl}
-     */
-    popParam() {
-        if (this.hasParams()) {
-            let dsl = PARAMS.get(this).pop();
-            PARENT.delete(dsl);
-        }
-        return this;
-    }
-
-    /**
-     * Has {Dsl} got params
-     * @returns {boolean}
-     */
-    hasParams() {
-        return PARAMS.get(this).length > 0;
-    }
-
-    /**
-     * Has {Dsl} got a name
-     * @return {boolean}
-     */
-    hasName() {
-        return NAME.has(this);
-    }
-
-    /**
-     * Has {Dsl} got a parent
-     * @returns {boolean}
-     */
-    hasParent() {
-        return PARENT.has(this);
-    }
-
-    /**
-     * Has {Dsl} got a next
-     * @returns {boolean}
-     */
-    hasNext() {
-        return NEXT.has(this);
-    }
-
-    /**
-     * Has {Dsl} got a prev
-     * @returns {boolean}
-     */
-    hasPrev() {
-        return PREV.has(this);
-    }
-
-    /**
-     * Has {Dsl} got a ref
-     * @return {boolean}
-     */
-    hasRef() {
-        return REF.has(this);
-    }
-
-    /**
-     * Has {Dsl} got issues
-     * @return {boolean}
-     */
-    hasIssues() {
-        return this.issues.length > 0;
-    }
-
-    /**
-     * Retrieves the Dsl for the last chained call
-     * @return {Dsl}
-     */
-    getLastMethod() {
-        if (this.next) {
-            return this.next.getLastMethod();
-        }
-        else {
-            return this;
-        }
-    }
-
-    /**
-     * Chain a function call to this Dsl
-     * @param method
-     * @return {Dsl}
-     */
-    chain(method) {
-        if (method instanceof Dsl) {
-            if (method.kind.family !== FAMILY.FUNCTION) {
-                throw new DslError('Cannot push method ... method must be a function');
-            }
-            let last = this.getLastMethod();
-            last.next = method;
-            method.prev = last;
-            return method;
-        } else {
-            throw new DslError('Cannot push method ... method must be of type Dsl');
-        }
-    }
-
-    /**
-     * Remove the last chained call from this Dsl
-     * @return {Dsl}
-     */
-    popChainedMethod() {
-        let last = this.getLastMethod();
-        if (last.hasPrev()) {
-            NEXT.delete(last.prev);
-            PREV.delete(last);
-        }
-        return this;
-    }
-
-    /**
-     * Get the root node for this Dsl
-     * @return {Dsl}
-     */
-    getRoot() {
-        if (this.hasParent()) {
-            return this.parent.getRoot();
-        }
-        else if (this.hasPrev()) {
-            return this.prev.getRoot();
-        }
-        else return this;
-    }
-
-    /**
-     * Equality comparison
-     * @param value
-     * @return {boolean}
-     */
-    equals(value) {
-        if (value && value instanceof Dsl) {
-            try {
-                if (this.kind === value.kind) {
-                    return this.toString() === value.toString();
-                }
-            } catch (err) {
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Return the DSL representation for this Dsl
-     * @return {string}
-     */
-    toString() {
-        let current = this;
-        let yaml = current.generate();
-        switch (current.kind.family) {
-            case FAMILY.FUNCTION:
-                yaml = FUNCTION_PREFIX + yaml;
-                // fallthrough to next case
-            case FAMILY.REFERENCE:
-                while (current.hasNext()) {
-                    current = current.next;
-                    yaml += '.' + current.generate();
-                }
-                break;
-            default:
-                // TODO check if we need unquoted string constants (they're in quotes now)
-        }
-        return yaml;
-    }
-    
-    toJSON() {
-        // note the result of this is serialized, as per JSON.stringify docs for an Object.toJSON:
-        // (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
-        // semantics of this method are toReplacedObjectForJsonSerialization _not_ toEncodedJsonString;
-        // so we want the toString
-        return this.toString();
-    }
-
-    /**
-     * Return a clone for this Dsl
-     * @return {Dsl}
-     */
-    clone() {
-        let clone = new Dsl(this.kind, this.name);
-        clone.relationships = Array.from(this.relationships);
-        clone.issues = Array.from(this.issues);
-        if (this.next) {
-            clone.next = this.next.clone();
-        }
-        this.params.forEach(param => clone.param(param.clone()));
-
-        return clone;
-    }
-
-    /**
-     * Return the DSL representation for this Dsl component (only)
-     * @return {string}
-     */
-    generate() {
-        if (this.kind.family === FAMILY.FUNCTION) {
-            return this.name + '(' + this.generateParams() + ')';
-        }
-        else if (this.kind === KIND.ENTITY) {
-            return 'entity("' + this.ref.id + '")';
-        }
-        else if (this.kind === KIND.STRING) {
-            return JSON.stringify(this.name);
-        }
-        else if (this.kind === KIND.PORT) {
-            // In case we get a single port range (i.e. 8080+, not part of a $brooklyn:...)
-            // we need to return the value without double quote, otherwise, Brooklyn won't accept it.
-            return this.hasParent() || this.hasNext() || this.hasPrev() ? JSON.stringify(this.name) : this.name;
-        }
-        return this.name;
-    }
-
-    /**
-     * Return the DSL representation for a list of parameters, comma separated
-     * @return {string}
-     */
-    generateParams() {
-        return this.params.map(param => param.toString()).join(', ');
-    }
-
-    /**
-     * Recursively visit this Dsl
-     * @param func a function to call on each node in the Dsl graph
-     */
-    visit(func) {
-        if (!this.visiting) {
-            this.visiting = true;
-            func(this);
-            this.params.forEach(p => p.visit(func));
-            if (this.hasPrev()) {
-                this.prev.visit(func);
-            }
-            if (this.hasNext()) {
-                this.next.visit(func);
-            }
-            this.visiting = false;
-        }
-    }
-
-    /**
-     * Retrieve the issues for this Dsl, recursively
-     */
-    getAllIssues() {
-        let allIssues = new Array();
-
-        this.visit(dsl => {
-            allIssues = allIssues.concat(dsl.issues);
-        });
-
-        return allIssues;
-    }
-
-    /**
-     * Retrieve the references contained in this Dsl, recursively
-     * @return {Array} an array containing either Entities or other Dsls that can be resolved to Entities
-     */
-    getReferences() {
-        let refs = new Set();
-
-        this.visit(dsl => {
-            if (dsl.kind === KIND.TARGET) {
-                if (!dsl.hasPrev() || dsl.prev.kind !== KIND.ENTITY) {
-                    // only add the leftmost dsl in a chain of entity functions
-                    refs.add(dsl);
-                }
-            }
-            else if (dsl.kind === KIND.ENTITY) {
-                refs.add(dsl.ref);
-            }
-        });
-
-        return Array.from(refs);
-    }
-
-    /**
-     * Retrieve the Entities referenced by this Dsl
-     * @param {Entity} entity the base Entity, used to resolve relative references
-     * @param {function} entityResolver a function to resolve an entity from an ID
-     * @return {Array} the Entities referenced by this Dsl
-     */
-    getRelationships(entity, entityResolver) {
-        let references = this.getReferences();
-        let entitySet = new Set();
-        for (const ref of references) {
-            if (ref instanceof Entity) {
-                entitySet.add(ref);
-            }
-            else if (ref instanceof Dsl) {
-                let resolvedEntity = ref.resolveEntity(entity, entityResolver);
-                if (resolvedEntity && resolvedEntity !== entity) { // don't add relationships to self
-                    entitySet.add(resolvedEntity);
-                }
-            }
-            else {
-                throw new Error('Invalid value type in dsl references: ' + typeof ref);
-            }
-        }
-        return Array.from(entitySet);
-    }
-
-    /**
-     * Resolve the Entity referenced by this Dsl
-     * @param {Entity} entity the base entity to resolve relative targets against
-     * @param {function} entityResolver a function to resolve an Entity from an ID
-     * @return {Entity}
-     */
-    resolveEntity(entity, entityResolver) {
-        let dsl = this;
-        let curr = entity;
-        while (dsl.kind === KIND.TARGET) {
-            switch (dsl.name) {
-                case 'self':
-                    break;
-                case 'parent':
-                    if (!curr.parent) {
-                        this.issues.push('The entity with ID <code>' + curr.id + '</code> does not have a parent');
-                    }
-                    curr = curr.parent;
-                    break;
-                case 'child':
-                case 'sibling':
-                case 'descendant':
-                case 'ancestor':
-                case 'entity':
-                case 'component': // component can have 1 or 2 params
-                    let name = dsl.params[dsl.params.length - 1].name;
-                    let resolvedEntity = entityResolver(name);
-                    if (resolvedEntity === null) {
-                        this.issues.push('The reference ID <code>' + name + '</code> does not exist');
-                    }
-                    curr = resolvedEntity;
-                    break;
-                case 'root':
-                case 'scopeRoot':
-                    curr = this.resolveRoot(curr);
-                    break;
-            }
-            if (dsl.hasNext()) {
-                // follow the call chain
-                dsl = dsl.next;
-            }
-            else {
-                break;
-            }
-        }
-        return curr;
-    }
-
-    /**
-     * Utility method to get the root from an Entity
-     * @param {Entity} e an Entity
-     * @return {Entity} the root Entity
-     */
-    resolveRoot(e) {
-        while (e.hasParent()) {
-            e = e.parent;
-        }
-        return e;
-    }
-
-}
-
-/**
- * A parser for Dsl expressions.
- */
-export class DslParser {
-    /**
-     * @param {*} s a Dsl expression to parse, e.g. a string
-     */
-    constructor(s) {
-        this.s = s;
-    }
-
-    /**
-     * Parse this expression, or throw if it is malformed.
-     * @param {Entity} entity the base Entity to resolve relative references from
-     * @param {function} entityResolver a function to resolve an entity from an ID
-     * @return {Dsl} the Dsl object representing this expression
-     */
-    parse(entity, entityResolver) {
-        if (this.s instanceof String || typeof this.s === 'string') {
-            return this.parseString(this.s.toString().trim(), entity, entityResolver);
-        }
-        // else ... TODO support JSON objects (for YAML syntax)
-        else {
-            throw new DslError("Unable to parse: " + typeof this.s);
-        }
-    }
-
-    /**
-     * Parse an expression in string form, or throw.
-     * @param {string} s the expression to parse
-     * @param {Entity} entity the base Entity to resolve relative references from
-     * @param {function} entityResolver a function to resolve an entity from an ID
-     * @return {Dsl} the Dsl object representing the expression in s
-     */
-    parseString(s, entity, entityResolver) {
-        let t = new Tokenizer(s);
-        let dsl = this.expression(t);
-        t.skipWhitespace();
-        if (!t.atEndOfInput()) {
-            throw new DslError('EXPRESSION followed by spurious content: ' + t.toJSON());
-        }
-        if (entity && entityResolver) {
-            dsl.relationships = dsl.getRelationships(entity, entityResolver);
-        }
-        return dsl;
-    }
-
-    /**
-     * Parse a Dsl expression, or throw.
-     * EXPRESSION ::= FUNCTION_CHAIN | CONSTANT
-     * @param {Tokenizer} t the current Tokenizer
-     * @return {Dsl} the Dsl object representing the expression
-     */
-    expression(t) {
-        if (t.peek(FUNCTION_PREFIX)) {
-            t.next(FUNCTION_PREFIX);
-            return this.functionChain(t);
-        }
-        else {
-            return this.constant(t);
-        }
-    }
-
-    /**
-     * Parse a Dsl function call chain, or throw.
-     * FUNCTION_CHAIN ::= FUNCTION_CALL { "." FUNCTION_CALL }*
-     * @param {Tokenizer} t the Tokenizer
-     * @return {Dsl} the Dsl object representing a function call chain
-     */
-    functionChain(t) {
-        let func = this.functionCall(t);
-        while (t.peek('.')) {
-            t.next('.');
-            func.chain(this.functionCall(t));
-        }
-        return func;
-    }
-
-    /**
-     * Parse a Dsl function call, or throw.
-     * FUNCTION_CALL ::= IDENTIFIER "(" [ EXPRESSION {"," EXPRESSION}* ] ")"
-     * @param {Tokenizer} t the Tokenizer
-     * @return {Dsl} the Dsl object representing the function call
-     */
-    functionCall(t) {
-        let name = t.nextIdentifier();
-        let dsl = new Dsl(this.functionKind(name), name);
-        t.next('(');
-        while (!t.atEndOfInput()) {
-            if (t.peek(')')) {
-                // end of params
-                break;
-            }
-            dsl.param(this.expression(t));
-            if (t.peek(',')) {
-                t.next(',');
-                if (t.atEndOfInput()) {
-                    throw new DslError('Expected: EXPRESSION but found: end-of-input');
-                }
-            }
-        }
-        t.next(')');
-        return dsl;
-    }
-
-    /**
-     * Parse a Dsl constant, e.g. a string or a number, or throw.
-     * @param {Tokenizer} t the Tokenizer
-     * @return {Dsl} the Dsl object representing the constant
-     */
-    constant(t) {
-        if (t.peek('"')) {
-            // a string in double quotes
-            return new Dsl(KIND.STRING, JSON.parse(t.nextQuotedString()));
-        }
-        else if (t.peek('\'')) {
-            // a string in single quotes (YAML syntax)
-            let s = t.nextSingleQuotedString();
-            // convert to double quoted string (JSON syntax)
-            s = '"' + s.replace(/^'/, '').replace(/'$/, '').replace(/"/g, '\\\"').replace(/''/g, "'") + '"';
-            return new Dsl(KIND.STRING, JSON.parse(s));
-        }
-        else if (t.peekPortRange()) {
-            // a port range
-            return new Dsl(KIND.PORT, t.nextPortRange());
-        }
-        else if (t.peekNumber()) {
-            // a floating point number
-            return new Dsl(KIND.NUMBER, t.nextNumber());
-        }
-        throw new DslError('Expected: CONSTANT but found: ' + t.toJSON());
-    }
-
-    /**
-     * Find the most appropriate KIND for a function, by comparing
-     * its name to all known TARGETS and UTILITIES.
-     * @param name the function name
-     * @return {*} one of KIND.TARGET, KIND.UTILITY, KIND.METHOD
-     */
-    functionKind(name) {
-        if (TARGETS.includes(name)) {
-            return KIND.TARGET;
-        }
-        else if (UTILITIES.includes(name)) {
-            return KIND.UTILITY;
-        }
-        return KIND.METHOD;
-    }
-}
-
-/**
- * A string tokenizer. Retrieves tokens, such as symbols, characters,
- * quoted strings, numbers, from a string.
- * It is used by the DslParser to parse complex Dsl expressions.
- */
-export class Tokenizer {
-    /**
-     * @param {string} s the string to tokenize
-     */
-    constructor(s) {
-        // this.s contains the current input buffer
-        this.s = s.trim();
-    }
-
-    /**
-     * Return <code>true</code> if there are no more characters in the input.
-     * @return {boolean}
-     */
-    atEndOfInput() {
-        return this.s.length === 0;
-    }
-
-    /**
-     * Fetch one identifier, such as a function name, or throw.
-     * @return {string}
-     */
-    nextIdentifier() {
-        this.skipWhitespace();
-        let spl = this.s.split(/\s|[^$A-Za-z0-9_]/, 1);
-        if (spl.length > 1) {
-            this.s = spl[1];
-            return spl[0];
-        }
-        else if (spl.length === 1) {
-            this.skipChars(spl[0].length);
-            return spl[0];
-        }
-        else {
-            throw new DslError('Expected IDENTIFIER but found: ' + this.toJSON());
-        }
-    }
-
-    /**
-     * Skip whitespace from the input.
-     */
-    skipWhitespace() {
-        // input was right trimmed, so this is effectively a left trim.
-        this.s = this.s.trim();
-    }
-
-    /**
-     * Fetch the requested symbol, or throw.
-     * @param {string} sym one or more non-whitespace characters, e.g.,
-     * a reserved keyword or a special character.
-     * @return {string}
-     */
-    next(sym) {
-        sym = sym.trim();
-        if (sym.length === 0) {
-            throw new DslError("Empty symbol");
-        }
-        this.skipWhitespace();
-        if (this.s.startsWith(sym)) {
-            this.skipChars(sym.length);
-            this.skipWhitespace();
-            return sym;
-        }
-        else {
-            throw new DslError('Expected: "' + sym + '" but found: ' + this.toJSON());
-        }
-    }
-
-    /**
-     * Fetch a string in double quotes, or throw.
-     * @return {string}
-     */
-    nextQuotedString() {
-        let str = this.next('"');
-        let prev = '';
-        let curr = '';
-        let terminated = false;
-        while (!this.atEndOfInput()) {
-            curr = this.nextChar();
-            str += curr;
-            if (prev !== '\\' && curr === '"') {
-                // end of string
-                terminated = true;
-                break;
-            }
-            prev = curr;
-        }
-        if (!terminated) {
-            throw new DslError('Unterminated quoted string');
-        }
-        return str;
-    }
-
-    /**
-     * Fetch a string in single quotes (YAML syntax), or throw.
-     * @return {string}
-     */
-    nextSingleQuotedString() {
-        let str = this.next('\'');
-        let prev = '';
-        let curr = '';
-        let terminated = false;
-        let doubles = false;
-        while (!this.atEndOfInput()) {
-            curr = this.nextChar();
-            str += curr;
-            if (curr === '\'') {
-                if (prev === '\'' && doubles === false) {
-                    doubles = true;
-                }
-                else if (this.peek('\'') === false) {
-                    // end of string
-                    terminated = true;
-                    break;
-                }
-            }
-            else {
-                doubles = false;
-            }
-            prev = curr;
-        }
-        if (!terminated) {
-            throw new DslError('Unterminated quoted string');
-        }
-        return str;
-    }
-
-    /**
-     * Fetch a literal number, or throw.
-     * @return {number}
-     */
-    nextNumber() {
-        let mm = this.s.match(numberRegex);
-        if (mm === null) {
-            throw new DslError('Expected: NUMBER but found: ' + this.toJSON());
-        }
-        this.skipChars(mm[0].length);
-        return Number(mm[0]);
-    }
-
-    /**
-     * Fetch a port range, or throw
-     * Valid port ranges are, e.g., <code>8080+</code> or <code>1024-32767</code>
-     * @return {string}
-     */
-    nextPortRange() {
-        let mm = this.s.match(portRangeRegex);
-        if (mm === null) {
-            throw new DslError('Expected: PORT_RANGE but found: ' + this.toJSON());
-        }
-        this.skipChars(mm[0].length);
-        return mm[0];
-    }
-
-    /**
-     * Fetch the next character in the input buffer, or throw.
-     * @return {string} the next character
-     */
-    nextChar() {
-        if (!this.atEndOfInput()) {
-            let c = this.s.charAt(0);
-            this.skipChars(1);
-            return c;
-        }
-        else {
-            throw new DslError('Expected CHAR but found: end-of-input');
-        }
-    }
-
-    /**
-     * Consume characters from the start of the input buffer
-     * @param num the number of characters to skip
-     */
-    skipChars(num) {
-        this.s = this.s.substring(num);
-    }
-
-    /**
-     * Return <code>true</code> if a call to <code>next(sym)</code> would succeed.
-     * @param {string} sym one or more characters, e.g.,
-     * a reserved keyword or a special character.
-     * @return {boolean}
-     */
-    peek(sym) {
-        return this.s.startsWith(sym);
-    }
-
-    /**
-     * Return <code>true</code> if a call to <code>nextPortRange()</code> would succeed.
-     * @return {boolean}
-     */
-    peekPortRange() {
-        return this.s.search(portRangeRegex) >= 0;
-    }
-
-    /**
-     * Return <code>true</code> if a call to <code>nextNumber()</code> would succeed.
-     * @return {boolean}
-     */
-    peekNumber() {
-        return this.s.search(numberRegex) >= 0;
-    }
-
-    /**
-     * Return a JSON of the current buffer.
-     * Mainly for diagnostic purposes.
-     * @return {string}
-     */
-    toJSON() {
-        // FIXME - this is not compatible with JSON.stringify which expects _unserialized_ object
-        // ie calling JSON.stringify(this) will result in _twice_ escaped JSON.
-        // but need to review uses of this method (in case there is a caller elsewhere who expects
-        // valid JSON).  [as noted above in other toJSON, the method name is horribly ambiguous!]
-        return JSON.stringify(this.s);
-    }
-}
-
-export class DslError extends Error {
-
-    constructor(message, options = {}) {
-        super(message);
-        this.name = 'DslError';
-        this.message = message;
-        this.id = options.id || 'general-error';
-        this.data = options.data || null;
-        if (typeof Error.captureStackTrace === 'function') {
-            Error.captureStackTrace(this, this.constructor);
-        } else {
-            this.stack = (new Error(message)).stack;
-        }
-    }
-}