Add parameters configuration UI for entities

Signed-off-by: Andrew Donald Kennedy <andrew.kennedy@cloudsoftcorp.com>
diff --git a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
index 38604a3..ecfefae 100644
--- a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
@@ -68,6 +68,7 @@
         populateEntityFromApi: populateEntityFromApiSuccess,
         populateLocationFromApi: populateLocationFromApiSuccess,
         addConfigKeyDefinition: addConfigKeyDefinition,
+        addParameterDefinition: addParameterDefinition,
         getRelationships: getRelationships,
     };
 
@@ -516,13 +517,23 @@
 
     function addConfigKeyDefinition(config, key) {
         config.push({
-            "constraints": [],
-            "description": "",
             "name": key,
             "label": key,
+            "description": "",
             "priority": 1,
             "pinned": true,
             "type": "java.lang.String",
+            "constraints": [],
+        });
+    }
+
+    function addParameterDefinition(params, key) {
+        params.push({
+            "name": key,
+            "description": "",
+            "type": "string",
+            "default": "",
+            "constraints": [],
         });
     }
 
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
index 53be4f2..7083173 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
@@ -82,28 +82,46 @@
             model: '='
         },
         controller: ['$scope', '$element', controller],
-        templateUrl: function (tElement, tAttrs) { 
-            return tAttrs.templateUrl || TEMPLATE_URL; 
+        templateUrl: function (tElement, tAttrs) {
+            return tAttrs.templateUrl || TEMPLATE_URL;
         },
         link: link,
         controllerAs: 'specEditor',
     };
 
     function controller($scope, $element) {
-        (composerOverrides.configureSpecEditorController || function() {})(this, $scope, $element);
-        
+        (composerOverrides.configureSpecEditorController || function () {
+        })(this, $scope, $element);
+
         // does very little currently, but link adds to this
         return this;
     }
 
     function link(scope, element, attrs, specEditor) {
         scope.specEditor = specEditor;
+        scope.getParameter = getParameter;
+        scope.addParameter = addParameter;
         scope.addConfigKey = addConfigKey;
         scope.FAMILIES = EntityFamily;
         scope.RESERVED_KEYS = RESERVED_KEYS;
         scope.REPLACED_DSL_ENTITYSPEC = REPLACED_DSL_ENTITYSPEC;
+        scope.parameters = [];
+        scope.config = {};
 
         let defaultState = {
+            parameters: {
+                add: {
+                    value: '',
+                    open: false
+                },
+                search: '',
+                focus: '',
+                filter: {
+                    open: false,
+                },
+                open: false,
+
+            },
             config: {
                 add: {
                     value: '',
@@ -112,7 +130,7 @@
                 search: '',
                 focus: '',
                 filter: {
-                    values: { suggested: true, required: true, inuse: true },
+                    values: {suggested: true, required: true, inuse: true},
                     open: false,
                 },
                 // TODO would be nice to set null here, then have it go true if there is filtered config
@@ -137,15 +155,17 @@
                 open: false
             }
         };
+
         // allow downstream to configure this controller and/or scope
-        (composerOverrides.configureSpecEditor || function() {})(specEditor, scope, element, $state, $compile, $templateCache);
+        (composerOverrides.configureSpecEditor || function () {
+        })(specEditor, scope, element, $state, $compile, $templateCache);
 
         scope.filters = {
-            config: CONFIG_FILTERS
+            config: CONFIG_FILTERS,
         };
-        scope.isFilterDisabled = (filter) => filter.id!='all' && scope.state.config.filter.values['all'];
+        scope.isFilterDisabled = (filter) => filter.id !== 'all' && scope.state.config.filter.values['all'];
         scope.onFilterClicked = (filter) => {
-            if (!scope.isFilterDisabled(filter)) scope.state.config.filter.values[ filter.id ] = !scope.state.config.filter.values[ filter.id ];
+            if (!scope.isFilterDisabled(filter)) scope.state.config.filter.values[filter.id] = !scope.state.config.filter.values[filter.id];
         };
 
         scope.state = sessionStorage && sessionStorage.getItem(scope.model._id)
@@ -160,25 +180,31 @@
                 }
             }
         }, true);
-        scope.$watch('state.config.filter.values', ()=> {
+        scope.$watch('state.config.filter.values', () => {
             scope.state.config.add.list = getAddListConfig();
         });
 
         loadCustomConfigWidgetMetadata(scope);
 
-        scope.config = {};
-        scope.$watch('model', (newVal, oldVal)=> {
+        // Model
+        scope.$watch('model', (newVal, oldVal) => {
             if (newVal && !newVal.equals(oldVal)) {
                 loadLocalConfigFromModel();
+                loadLocalParametersFromModel();
             }
         }, true);
-
-        scope.$watch('model.id', ()=> {
+        scope.$watch('model.id', () => {
             blueprintService.refreshAllRelationships();
         });
 
+        // Parameters
+        scope.$watch('parameters', (newVal, oldVal) => {
+            setModelFromLocalParameters();
+            scope.model.clearIssues({group: 'parameters'});
+        }, true);
+
         // Config
-        scope.$watch('config', (newVal, oldVal)=> {
+        scope.$watch('config', (newVal, oldVal) => {
             setModelFromLocalConfig();
             scope.model.clearIssues({group: 'config'});
             blueprintService.refreshRelationships(scope.model).then(() => {
@@ -188,12 +214,13 @@
             });
         }, true);
 
-        scope.getObjectSize = (object)=> {
-            return specEditor.defined(object) && object!=null ? Object.keys(object).length : 0;
+        scope.getObjectSize = (object) => {
+            return specEditor.defined(object) && object != null ? Object.keys(object).length : 0;
         };
+
         function findNext(element, clazz, stopClazz) {
             let el = element, last = null;
-            while (last!=el) {
+            while (last !== el) {
                 last = el;
                 if (el.children().length) {
                     el = angular.element(el.children()[0]);
@@ -210,13 +237,14 @@
             }
             return null;
         }
+
         function findPrev(element, clazz, stopClazz) {
             let el = element, last = null;
-            while (last!=el) {
+            while (last !== el) {
                 last = el;
                 if (el.children().length) {
                     // this still does children first, not ideal but okay for how we use it
-                    el = angular.element(el.children()[el.children().length-1]);
+                    el = angular.element(el.children()[el.children().length - 1]);
                 } else {
                     while (el.length && !el[0].previousElementSibling) {
                         el = el.parent();
@@ -230,9 +258,10 @@
             }
             return null;
         }
+
         function findAncestor(element, clazz, stopClazz) {
             let el = element, last = null;
-            while (last!=el) {
+            while (last !== el) {
                 last = el;
                 el = el.parent();
                 if (el.hasClass(clazz)) {
@@ -242,6 +271,7 @@
             }
             return null;
         }
+
         function focusIfPossible(event, next) {
             if (next && next.length) {
                 if (event) event.preventDefault();
@@ -252,6 +282,7 @@
                 return false;
             }
         }
+
         scope.nonempty = (o) => o && Object.keys(o).length;
         scope.defined = specEditor.defined = (o) => (typeof o !== 'undefined');
         specEditor.advanceOutToFormGroupInPanel = (element, event) => {
@@ -259,10 +290,10 @@
         };
         specEditor.advanceControlInFormGroup = (element, event) => {
             focusIfPossible(event, findNext(element, "form-control", "form-group")) ||
-                specEditor.advanceOutToFormGroupInPanel(element, event);
+            specEditor.advanceOutToFormGroupInPanel(element, event);
         };
 
-        scope.onAddMapProperty = (configKey, key, ev)=> {
+        scope.onAddMapProperty = (configKey, key, ev) => {
             if (key) {
                 if (!scope.config[configKey]) {
                     scope.config[configKey] = {};
@@ -275,7 +306,7 @@
                         let element = angular.element(ev.target);
                         let prev = findPrev(element, "form-control", "form-group");
                         focusIfPossible(null, prev) ||
-                            specEditor.advanceOutToFormGroupInPanel(element, null);
+                        specEditor.advanceOutToFormGroupInPanel(element, null);
                     }, 0);
                 } else {
                     // user entered a key that already exists;
@@ -284,16 +315,16 @@
             }
         };
         scope.cycleExpandMode = specEditor.cycleExpandMode = function (expandMode, ctx, item, focus) {
-            return expandMode == 'default' ? 'open' :
-                expandMode == 'open' ? 'closed' :
-                'default';
-        }
+            return expandMode === 'default' ? 'open' :
+                expandMode === 'open' ? 'closed' :
+                    'default';
+        };
         scope.onDeleteMapProperty = specEditor.onDeleteMapProperty = function (model, key) {
             if (model && model.hasOwnProperty(key)) {
                 delete model[key];
             }
         };
-        scope.onAddListItem = specEditor.onAddListItem = (configKey, item, ev)=> {
+        scope.onAddListItem = specEditor.onAddListItem = (configKey, item, ev) => {
             if (item) {
                 if (!scope.config[configKey]) {
                     scope.config[configKey] = [];
@@ -309,14 +340,14 @@
                 model.splice(index, 1);
             }
         };
-        scope.isConfigHidden = specEditor.isConfigHidden = (config)=> {
+        scope.isConfigHidden = specEditor.isConfigHidden = (config) => {
             let allConfig = scope.model.miscData.get('config');
             if (allConfig.indexOf(config) === -1) {
                 return false;
             }
             return $filter('specEditorConfig')(allConfig, scope.state.config.filter.values).indexOf(config) === -1;
         };
-        scope.onFocusOnConfig = specEditor.onFocusOnConfig = ($item)=> {
+        scope.onFocusOnConfig = specEditor.onFocusOnConfig = ($item) => {
             scope.state.config.search = '';
             scope.state.config.add.value = '';
             scope.state.config.add.open = false;
@@ -325,14 +356,23 @@
                 scope.state.config.filter.values.all = true;
             }
         };
-        scope.recordFocus = specEditor.recordFocus = ($item) => {
+        scope.onFocusOnParameter = specEditor.onFocusOnParameter = ($item) => {
+            scope.state.parameters.search = '';
+            scope.state.parameters.add.value = '';
+            scope.state.parameters.add.open = false;
+            scope.state.parameters.focus = $item.name;
+        };
+        scope.recordConfigFocus = specEditor.recordConfigFocus = ($item) => {
             scope.state.config.focus = $item.name;
         };
+        scope.recordParameterFocus = specEditor.recordParameterFocus = ($item) => {
+            scope.state.parameters.focus = $item.name;
+        };
 
-        scope.removeAdjunct = ($event, adjunct)=> {
+        scope.removeAdjunct = ($event, adjunct) => {
             $event.preventDefault();
             $event.stopPropagation();
-            switch(adjunct.family) {
+            switch (adjunct.family) {
                 case EntityFamily.POLICY:
                     scope.model.removePolicy(adjunct._id);
                     break;
@@ -342,7 +382,7 @@
             }
         };
 
-        scope.removeModel = ()=> {
+        scope.removeModel = () => {
             switch (scope.model.family) {
                 case EntityFamily.ENRICHER:
                     scope.model.parent.removeEnricher(scope.model._id);
@@ -357,25 +397,30 @@
             $state.go(graphicalState.name);
         };
 
-        scope.getConfigIssues = specEditor.getConfigIssues = ()=> {
+        scope.getParameterIssues = specEditor.getParameterIssues = () => {
             return scope.model.issues
-                .filter((issue)=>(issue.group === 'config'))
+                .filter((issue) => (issue.group === 'parameters'))
                 .concat(Object.values(scope.model.getClusterMemberspecEntities())
-                    .filter((spec)=>(spec && spec.hasIssues()))
-                    .reduce((acc, spec)=>(acc.concat(spec.issues)), []));
+                    .filter((spec) => (spec && spec.hasIssues()))
+                    .reduce((acc, spec) => (acc.concat(spec.issues)), []));
         };
-
-        scope.getPoliciesIssues = ()=> {
-            return scope.model.getPoliciesAsArray().reduce((acc, policy)=> {
+        scope.getConfigIssues = specEditor.getConfigIssues = () => {
+            return scope.model.issues
+                .filter((issue) => (issue.group === 'config'))
+                .concat(Object.values(scope.model.getClusterMemberspecEntities())
+                    .filter((spec) => (spec && spec.hasIssues()))
+                    .reduce((acc, spec) => (acc.concat(spec.issues)), []));
+        };
+        scope.getPoliciesIssues = () => {
+            return scope.model.getPoliciesAsArray().reduce((acc, policy) => {
                 if (policy.hasIssues()) {
                     acc = acc.concat(policy.issues)
                 }
                 return acc;
             }, []);
         };
-
-        scope.getEnrichersIssues = ()=> {
-            return scope.model.getEnrichersAsArray().reduce((acc, enricher)=> {
+        scope.getEnrichersIssues = () => {
+            return scope.model.getEnrichersAsArray().reduce((acc, enricher) => {
                 if (enricher.hasIssues()) {
                     acc = acc.concat(enricher.issues)
                 }
@@ -393,7 +438,7 @@
                 out.push('<div class="paragraph-spacing"></div>');
                 out.push($sanitize(item));
             }
-            out.splice(0,1);
+            out.splice(0, 1);
             return $sce.trustAsHtml(out.join("\n"));
         };
 
@@ -426,20 +471,20 @@
                 // override to use string editor if the editor doesn't support the value
                 // (probably this is an error, though type-coercion might make it not so)
                 if (type === 'boolean') {
-                    if (typeof val !== type) return type+'-manual';  // causes default string editor
+                    if (typeof val !== type) return type + '-manual';  // causes default string editor
                 } else if (type === 'map') {
-                    if (typeof val !== 'object') return type+'-manual';  // causes default string editor
+                    if (typeof val !== 'object') return type + '-manual';  // causes default string editor
                 } else if (type === 'array') {
-                    if (!Array.isArray(val)) return type+'-manual';  // causes default string editor
+                    if (!Array.isArray(val)) return type + '-manual';  // causes default string editor
                 }
             }
             if (scope.state.config.codeModeActive[item.name]) {
                 // code mode forces manual editor
-                return type+'-manual';
+                return type + '-manual';
             }
 
             return type;
-        };
+        }
         scope.getConfigWidgetMode = (item, value) => {
             // record the value as `item.widgetMode` so we can reference it subsequently, as well as returning it
 
@@ -449,13 +494,25 @@
 
             // if type isn't set then infer
             if (value instanceof Array) {
-                definition.widgetMode = 'array';
+                item.widgetMode = 'array';
             } else if (value instanceof Object) {
-                definition.widgetMode = 'map';
+                item.widgetMode = 'map';
             } else {
-                definition.widgetMode = 'unknown';
+                item.widgetMode = 'unknown';
             }
-        }
+            return item.widgetMode;
+        };
+        scope.getParameterWidgetMode = (item) => {
+            let type = item.type || item.typeName;
+
+            if (type === 'java.lang.Boolean') type = 'boolean';
+            else if (type === 'java.util.Map') type = 'map';
+            else if (type === 'java.util.Set' || type === 'java.util.List' || type === 'java.util.Collection' || type.startsWith('List<')) type = 'array';
+
+            if (type.startsWith('AWS::')) type = 'unknown';
+
+            return type;
+        };
         scope.isCodeModeAvailable = (item) => {
             let val = scope.config[item.name];
 
@@ -491,7 +548,7 @@
             if (val instanceof Array || val instanceof Object) return true;
             // other primitive
             return false;
-        }
+        };
         scope.codeModeClick = (item) => {
             if (scope.state.config.codeModeForced[item.name] && scope.state.config.codeModeActive[item.name]) {
                 // if forced and active, don't allow clicks
@@ -503,7 +560,7 @@
             if (!specEditor.defined(value)) {
                 value = null;
             }
-            if (value!=null) {
+            if (value != null) {
                 if (oldMode) {
                     // leaving code mode
                     try {
@@ -548,43 +605,43 @@
                         value = JSON.stringify(value);
                     }
                 }
-                if (value!=null) {
+                if (value != null) {
                     scope.config[item.name] = value;
                 }
             }
             scope.state.config.codeModeActive[item.name] = !oldMode;
-            if (value!=null) {
+            if (value != null) {
                 // local config changed, make sure model is updated too
                 setModelFromLocalConfig();
             }
         };
         scope.getJsonModeTitle = (itemName) => {
             if (!scope.state.config.codeModeActive[itemName]) {
-                return "Treat this value as a JSON-encoded object ["+itemName+"]";
+                return "Treat this value as a JSON-encoded object [" + itemName + "]";
             }
             if (scope.state.config.codeModeForced[itemName]) {
-                return "This data is a complex object and can only be entered as JSON ["+itemName+"]";
+                return "This data is a complex object and can only be entered as JSON [" + itemName + "]";
             } else {
-                return "Edit in simple mode, unwrapping JSON if possible ["+itemName+"]";
+                return "Edit in simple mode, unwrapping JSON if possible [" + itemName + "]";
             }
         };
         /** returns 'enabled' or 'disabled' if a widget is defined, or null if no special widget is defined */
         specEditor.getCustomConfigWidgetMode = (item) => {
-            var widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
+            let widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
             if (!widgetMetadata || widgetMetadata["error"]) return null;
             return widgetMetadata["enabled"] ? 'enabled' : 'disabled';
         };
         specEditor.toggleCustomConfigWidgetMode = (item, newval) => {
-            var widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
+            let widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
             if (!widgetMetadata) {
-                $log.error('Custom widget mode should not be toggled when not available: '+item.name);
+                $log.error('Custom widget mode should not be toggled when not available: ' + item.name);
                 return null;
             }
             if (!specEditor.defined(newval)) newval = !widgetMetadata.enabled;
             widgetMetadata.enabled = newval;
-        }
+        };
         specEditor.getCustomConfigWidgetModeTitle = (item) => {
-            var widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
+            let widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
             if (!widgetMetadata) {
                 // shouldn't be visible
                 return "(custom widget not available)";
@@ -592,18 +649,20 @@
             return widgetMetadata.enabled ? "Use standard widget" : "Use custom widget";
         };
         specEditor.getCustomConfigWidgetTemplate = (item) => {
-            var widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
-            var widgetName = $sanitize(widgetMetadata.widget || '--no-widget--');
-            var templateName = 'custom-config-widget-'+widgetName;
+            let widgetMetadata = scope.state.config.customConfigWidgetMetadata[item.name];
+            let widgetName = $sanitize(widgetMetadata.widget || '--no-widget--');
+            let templateName = 'custom-config-widget-' + widgetName;
             if (!$templateCache.get(templateName)) {
-                var widgetDirective = widgetName.replace(/(-[a-z])/g, function($1){return $1[1].toUpperCase();})+'Directive';
+                let widgetDirective = widgetName.replace(/(-[a-z])/g, function ($1) {
+                    return $1[1].toUpperCase();
+                }) + 'Directive';
                 if ($injector.has(widgetDirective)) {
-                    $templateCache.put(templateName, '<'+widgetName+' item="item" params="state.config.customConfigWidgetMetadata[item.name]" config="config" model="model"/>');
+                    $templateCache.put(templateName, '<' + widgetName + ' item="item" params="state.config.customConfigWidgetMetadata[item.name]" config="config" model="model"/>');
                 } else {
-                    $log.error('Missing directive '+widgetDirective+' for custom widget for '+item.name+'; falling back to default widget');
+                    $log.error('Missing directive ' + widgetDirective + ' for custom widget for ' + item.name + '; falling back to default widget');
                     scope.state.config.customConfigWidgetMetadata[item.name].error = "Missing directive";
                     templateName = "error-" + templateName;
-                    $templateCache.put(templateName, '<i>Widget '+widgetName+' missing</i>');
+                    $templateCache.put(templateName, '<i>Widget ' + widgetName + ' missing</i>');
                 }
             }
             return templateName;
@@ -611,7 +670,7 @@
 
         specEditor.isDsl = (key, index) => {
             let val = scope.model.config.get(key);
-            if (specEditor.defined(val) && specEditor.defined(index) && index!=null) val = val[index];
+            if (specEditor.defined(val) && specEditor.defined(index) && index != null) val = val[index];
             return specEditor.isDslVal(val);
         };
         specEditor.isDslVal = (val) => {
@@ -620,9 +679,9 @@
         };
         specEditor.isDslWizardButtonAllowed = (key, index, nonModelValue) => {
             let val = scope.model.config.get(key);
-            if (specEditor.defined(val) && specEditor.defined(index) && index!=null) val = val[index];
-            if (!specEditor.defined(val) || val===null || val==='') val = nonModelValue;
-            if (!specEditor.defined(val) || val===null || val==='') return true;
+            if (specEditor.defined(val) && specEditor.defined(index) && index != null) val = val[index];
+            if (!specEditor.defined(val) || val === null || val === '') val = nonModelValue;
+            if (!specEditor.defined(val) || val === null || val === '') return true;
             if (specEditor.isDslVal(val)) {
                 return true;
             }
@@ -635,39 +694,47 @@
                 return [];
             }
             let filteredConfig = $filter('specEditorConfig')(scope.model.miscData.get('config'), scope.state.config.filter.values, scope.model);
-            return scope.model.miscData.get('config').map((config)=> {
+            return scope.model.miscData.get('config').map((config) => {
                 config.isHidden = scope.model.miscData.get('config').indexOf(config) > -1 ? filteredConfig.indexOf(config) === -1 : false;
                 return config;
             });
         }
 
         function loadCustomConfigWidgetMetadata(model) {
-            var customConfigWidgets = (scope.model.miscData.get('ui-composer-hints') || {})['config-widgets'] || [];
-            customConfigWidgets.forEach( (wd) => {
-                var keys = wd.keys || [ wd.key ];
-                keys.forEach( (k) => {
-                    scope.state.config.customConfigWidgetMetadata[k] = angular.extend({ enabled: true }, scope.state.config.customConfigWidgetMetadata[k], wd);
+            let customConfigWidgets = (scope.model.miscData.get('ui-composer-hints') || {})['config-widgets'] || [];
+            customConfigWidgets.forEach((wd) => {
+                let keys = wd.keys || [wd.key];
+                keys.forEach((k) => {
+                    scope.state.config.customConfigWidgetMetadata[k] = angular.extend({enabled: true}, scope.state.config.customConfigWidgetMetadata[k], wd);
                 });
             });
         }
 
-        /* config state for each item is stored in multiple places:
-         * * scope.config = map of values used/set by editor (strings, map of strings, json code if using code mode, etc);
-         *   this should be suitable for ng-model to work with, so e.g. if using code mode we need to put JSON.stringify value in here,
-         *   and note any change here immediately (on next digest) updates scope.model.config, which e.g. in code mode
-         *   will JSON.parse
-         * * scope.model.config = map of values used in internal model
-         * * scope.model.miscData.get('config') = list of config keys with their metadata, including derived widgetMode
-         * * scope.state.config.{codeModeActive,dslManualOverride} = maps of booleans where edit modes are set and remembered for configs
+        /**
+         * The configuration data for each item is stored in multiple places:
+         *
+         * scope.config
+         *   A map of values used/set by editor, which can be strings, a map of strings, the JSON representation of the
+         *   object if using code mode, etc. This should be suitable for ng-model to work with, so e.g. if using code
+         *   mode we need to put JSON.stringify value in here, and note any change here immediately (on next digest)
+         *   updates scope.model.config, which e.g. in code mode will JSON.parse
+         *
+         * scope.model.config
+         *   A map of values used in internal model
+         *
+         * scope.model.miscData.get('config')
+         *   A list of config keys with their metadata, including derived widgetMode
+         *
+         * scope.state.config.{codeModeActive,dslManualOverride}
+         *   Maps of booleans where edit modes are set and remembered for configs
          */
 
         function loadLocalConfigFromModel() {
-            let map = scope.model.config;
+            let modelConfig = scope.model.config;
             let result = {};
-            for (let [key, value] of map) {
+            for (let [key, value] of modelConfig) {
                 if (blueprintService.isReservedKey(key)) {
-                    // skip
-                    continue;
+                    continue; // skip
                 }
 
                 result[key] = getLocalConfigValueFromModelValue(key, value);
@@ -676,7 +743,7 @@
         }
 
         function getLocalConfigValueFromModelValue(key, value) {
-            if (!specEditor.defined(value) || value==null) {
+            if (!specEditor.defined(value) || value == null) {
                 return value;
             }
 
@@ -691,7 +758,6 @@
 
             let definition = scope.model.miscData.get('config').find(config => config.name === key);
             if (!definition) {
-                // odd, no def'n for this key
                 definition = {};
                 scope.getConfigWidgetMode(definition, value)
             } else {
@@ -711,7 +777,7 @@
                 // also supporting yaml and comments, but that is a bigger task!)
                 if (scope.config && typeof scope.config[key] === 'string') {
                     try {
-                        if (JSON.stringify(JSON.parse(scope.config[key]))===JSON.stringify(value)) {
+                        if (JSON.stringify(JSON.parse(scope.config[key])) === JSON.stringify(value)) {
                             return scope.config[key];
                         }
                     } catch (ignoredError) {
@@ -731,7 +797,7 @@
                     return value.map(item => {
                         if (item instanceof Dsl) {
                             return item.toString();
-                        } else if (item instanceof Array  || item instanceof Object) {
+                        } else if (item instanceof Array || item instanceof Object) {
                             throw 'not simple json in array';
                         } else {
                             return item;
@@ -740,7 +806,7 @@
                 } else if (definition.widgetMode === 'map') {
                     let object = {};
                     for (let keyObject in value) {
-                        if (value[keyObject]  instanceof Dsl) {
+                        if (value[keyObject] instanceof Dsl) {
                             object[keyObject] = value[keyObject].toString();
                         } else if (value[keyObject] instanceof Array || value[keyObject] instanceof Object) {
                             throw 'not simple json in map';
@@ -769,11 +835,11 @@
             }
 
             // all other primitives treat as string (as they will get a string-based widget)
-            return ""+value;
+            return "" + value;
         }
 
         function getModelValueFromString(val) {
-            if (!specEditor.defined(val) || val==null || typeof val !== 'string') {
+            if (!specEditor.defined(val) || val == null || typeof val !== 'string') {
                 // only strings will have primitive inference applied
                 // (and this is only invoked when not in code mode)
                 return val;
@@ -810,7 +876,6 @@
 
                 let definition = scope.model.miscData.get('config').find(config => config.name === keyRef);
                 if (!definition) {
-                    // odd, no def'n for this key; infer
                     definition = {};
                     scope.getConfigWidgetMode(definition, localConfig[keyRef])
                 }
@@ -856,7 +921,7 @@
                 loadLocalConfigFromModel();
                 scope.state.config.add.value = '';
                 scope.state.config.add.open = false;
-                scope.state.config.filter.values[ CONFIG_FILTERS[CONFIG_FILTERS.length - 1].id ] = true;
+                scope.state.config.filter.values[CONFIG_FILTERS[CONFIG_FILTERS.length - 1].id] = true;
                 scope.state.config.focus = name;
             }
         }
@@ -869,13 +934,68 @@
 
                 // If we need an integer, check if the value is a number
                 if (config.type === 'java.lang.Integer' && !angular.isNumber(value) && !(value instanceof Dsl)) {
-                    model.addIssue(Issue.builder().group('config').ref(key).message('<code>'+$sanitize(value)+'</code> is not a number').build());
+                    model.addIssue(Issue.builder().group('config').ref(key).message('<code>' + $sanitize(value) + '</code> is not a number').build());
                 }
                 if (scope.state.config.codeModeError[key]) {
-                    model.addIssue(Issue.builder().group('config').ref(key).message('<code>'+$sanitize(value)+'</code> is not valid JSON').build());
+                    model.addIssue(Issue.builder().group('config').ref(key).message('<code>' + $sanitize(value) + '</code> is not valid JSON').build());
                 }
             });
         }
+
+        /**
+         * The parameter data for each item is stored in multiple places, similar to configuration data:
+         *
+         * scope.parameters
+         *   An array of values used/set by editor
+         *
+         * scope.model.parameters
+         *   An array of values used in internal model
+         *
+         * scope.model.miscData.get('parameters')
+         *   A list of parameters with their metadata, including derived widgetMode
+         */
+
+        function loadLocalParametersFromModel() {
+            let modelParams = scope.model.parameters;
+            let result = [];
+            for (let paramRef of modelParams) {
+                if (blueprintService.isReservedKey(paramRef.name)) {
+                    continue; // skip
+                }
+                result.push(paramRef);
+            }
+            scope.parameters = result;
+        }
+
+        function getParameter(name) {
+            return scope.parameters.find(p => p.name === name);
+        }
+
+        function setModelFromLocalParameters() {
+            let localParams = scope.parameters;
+            let result = [];
+            for (let paramRef of localParams) {
+                if (angular.isUndefined(paramRef) || paramRef === null || paramRef.length < 1) {
+                    continue;
+                }
+                result.push(paramRef);
+            }
+            scope.model.setParametersFromJson(result);
+        }
+
+        function addParameter() {
+            let name = scope.state.parameters.add.value;
+            if (name) {
+                let allParams = scope.model.miscData.get('parameters');
+                blueprintService.addParameterDefinition(allParams, name);
+                let param = allParams.find(p => p.name === name);
+                scope.model.addParameter(param);
+                loadLocalParametersFromModel();
+                scope.state.parameters.add.value = '';
+                scope.state.parameters.add.open = false;
+                scope.state.parameters.focus = name;
+            }
+        }
     }
 }
 
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
index 31af6f8..a3ed9ae 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
@@ -51,6 +51,112 @@
   </div>
 </section>
 
+
+<!-- ENTITY PARAMETERS -->
+<br-collapsible ng-if="true" state="state.parameters.open">  <!-- the ng-if is needed to make state update?! -->
+    <heading>
+        Parameters
+        <span ng-if="getParameterIssues().length> 0" class="badge" ng-class="getBadgeClass(getParameterIssues())">{{getParameterIssues().length}}</span>
+        <span class="pull-right" ng-show="$parent.stateWrapped.state">
+            <span class="spec-toolbar-action" ng-class="{'active': state.parameters.filter.open}"><i class="fa fa-filter collapsible-action" title="Filter parameters" ng-click="$event.stopPropagation(); $event.preventDefault(); state.parameters.filter.open = !state.parameters.filter.open" ng-class="{'text-info': state.parameters.search.length > 0}"></i></span>
+            <span class="spec-toolbar-action"><i class="fa fa-plus collapsible-action" title="Add parameter" ng-click="$event.stopPropagation(); $event.preventDefault(); state.parameters.add.open = !state.parameters.add.open"></i></span>
+        </span>
+    </heading>
+
+    <fieldset uib-collapse="!state.parameters.filter.open" class="spec-configuration-filters">
+        <div class="form-group">
+            <input ng-model="state.parameters.search" type="text" class="form-control" placeholder="Search for parameter by name" auto-focus="state.parameters.filter.open" blur-on-enter />
+        </div>
+    </fieldset>
+
+    <fieldset uib-collapse="!state.parameters.add.open" class="spec-configuration-add">
+        <input auto-focus="state.parameters.add.open"
+               type="text"
+               autocomplete="off"
+               ng-model="state.parameters.add.value"
+               placeholder="Add a new parameter or open existing"
+               class="form-control"
+               uib-typeahead="parameter.name for parameter in state.parameters.add.list | filter:{name:$viewValue}"
+               typeahead-template-url="blueprint-composer/component/spec-editor/parameter-item.html"
+               typeahead-editable="true"
+               typeahead-show-hint="true"
+               typeahead-min-length="0"
+               typeahead-select-on-blur="true"
+               typeahead-on-select="onFocusOnParameter($item, $model, $label, $event)"
+               typeahead-no-results="noResults"
+               blur-on-enter />
+
+        <div ng-if="state.parameters.add.value.length > 0 && noResults" uib-dropdown uib-dropdown-toggle auto-close="disabled" is-open="true">
+            <ul class="dropdown-menu">
+                <!-- mimic real dropdown if nothing found -->
+                <li class="uib-typeahead-match"><a ng-click="addParameter()">
+                    <div class="dropdown-item" ng-init="item = match.model">
+                        <div class="dropdown-row">
+                            <span ng-bind-html="state.parameters.add.value | uibTypeaheadHighlight:query" class="config-name"></span>
+                            <i class="fa fa-fw fa-plus-square-o"></i>
+                        </div>
+                </a></li>
+            </ul>
+        </div>
+    </fieldset>
+
+    <div class="spec-configuration">
+        <div class="spec-empty-state" ng-if="filteredItems.length === 0">
+            <div ng-if="model.miscData.get('parameters').length === 0">
+                <h4>No parameters</h4>
+            </div>
+            <div ng-if="model.miscData.get('parameters').length > 0">
+                <h4>No matching parameters</h4>
+                <p class="buttons">
+                    <button class="btn btn-sm btn-default" ng-if="state.parameters.search.length > 0" ng-click="state.parameters.search = ''">Clear search</button>
+                    <button class="btn btn-sm btn-success" ng-if="!state.parameters.filter.values.all" ng-click="state.parameters.filter.values.all = true">
+                        <i class="fa fa-filter"></i> Display all parameter options
+                    </button>
+                </p>
+            </div>
+        </div>
+
+        <form name="formSpecParameter" novalidate class="lightweight">
+            <div ng-repeat="item in (filteredItems = (model.miscData.get('parameters') | filter:{name:state.parameters.search} | orderBy:+priority)) track by item.name ">
+                <div class="form-group" ng-class="{'has-error': (model.issues | filter:{ref: item.name}:true).length > 0, 'used': getParameter(item.name) !== undefined}"
+                     ng-switch="getParameterWidgetMode(item)"
+                     tabindex="1"
+                     auto-focus="state.parameters.focus === item.name"
+                     auto-focus-listen-to-window="true"
+                     auto-focus-not-if-within="true"
+                     ng-focus="specEditor.recordParameterFocus(item)"
+                     on-enter="specEditor.advanceControlInFormGroup">
+
+                    <!-- have to hide it; excluding it conditionally via if doesn't play nice with switch -->
+                    <div class="config-flex-row">
+                        <label class="control-label" for="{{item.name}}">
+                            <span class="label-spec-configuration">{{item.label || item.name}}</span>
+                            <span class="info-spec-configuration">
+                                <i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'"
+                                   popover-title="{{item.label || item.name}}"
+                                   uib-popover-template="'blueprint-composer/component/spec-editor/parameter-info.html'"
+                                   popover-class="spec-editor-popover" popover-placement="top-left" popover-append-to-body="true"></i>
+                            </span>
+                        </label>
+
+                        <span class="label-rhs-buttons">
+                            <span class="spacer"> </span>
+                            <span class="remove-spec-configuration" ng-if="getParameter(item.name) !== undefined">
+                                <i class="fa fa-fw fa-eraser" ng-click="model.removeParameter(item.name)" aria-hidden="true" title="Clear parameter [{{item.name}}]"></i>
+                                <span class="sr-only">Clear parameter [{{item.name}}]</span>
+                            </span>
+                        </span>
+
+                        <!-- TODO use the code editor for defining parameter properties -->
+
+                        <small ng-repeat="issue in model.issues | filter:{ref: item.name}:true" class="help-block issue" ng-bind-html="issue.message"></small>
+                    </div>
+                </div>
+            </div>
+        </form>
+    </div>
+</br-collapsible>
+
 <!-- ENTITY CONFIGURATION -->
 <br-collapsible ng-if="true" state="state.config.open">  <!-- the ng-if is needed to make state update?! -->
     <heading>
@@ -133,7 +239,7 @@
                         auto-focus="state.config.focus === item.name"
                         auto-focus-listen-to-window="true"
                         auto-focus-not-if-within="true"
-                        ng-focus="specEditor.recordFocus(item)"
+                        ng-focus="specEditor.recordConfigFocus(item)"
                         on-enter="specEditor.advanceControlInFormGroup">
                   <div ng-if="specEditor.getCustomConfigWidgetMode(item) === 'enabled'" class="custom-config-widget">
                     <ng-include src="specEditor.getCustomConfigWidgetTemplate(item)" class="custom-config-widget"/>
@@ -180,10 +286,10 @@
                     <div class="control-value" ng-class="{ 'inline-control': item.widgetMode==='boolean', 'unset': !defined(config[item.name]), 'has-default': defined(item.defaultValue) && item.defaultValue!=null, 'code-mode-active': state.config.codeModeActive[item.name] }">
                         <div ng-switch-when="boolean" class="boolean">
                             <div class="btn-group btn-block" role="group">
-                                <button type="button" class="btn btn-xs btn-default" ng-class="{'btn-success active': config[item.name] === false, 'active': config[item.name] === undefined && item.defaultValue === false}" ng-click="config[item.name] = false" ng-focus="specEditor.recordFocus(item)">false</button>
-                                <button type="button" class="btn btn-xs btn-default" ng-class="{'btn-success active': config[item.name] === true, 'active': config[item.name] === undefined && item.defaultValue === true}" ng-click="config[item.name] = true" ng-focus="specEditor.recordFocus(item)">true</button>
+                                <button type="button" class="btn btn-xs btn-default" ng-class="{'btn-success active': config[item.name] === false, 'active': config[item.name] === undefined && item.defaultValue === false}" ng-click="config[item.name] = false" ng-focus="specEditor.recordConfigFocus(item)">false</button>
+                                <button type="button" class="btn btn-xs btn-default" ng-class="{'btn-success active': config[item.name] === true, 'active': config[item.name] === undefined && item.defaultValue === true}" ng-click="config[item.name] = true" ng-focus="specEditor.recordConfigFocus(item)">true</button>
                                 <span class="input-group-btn dsl-wizard-button" ng-if="specEditor.isDslWizardButtonAllowed(item.name)">
-                                    <a ui-sref="main.graphical.edit.dsl({entityId: model._id, for: item.name})" class="btn btn-xs btn-default" ng-class="{'btn-success active': config[item.name].length > 0}" title="Open in DSL editor" ng-focus="specEditor.recordFocus(item)"><i class="fa fa-bolt"></i></a>
+                                    <a ui-sref="main.graphical.edit.dsl({entityId: model._id, for: item.name})" class="btn btn-xs btn-default" ng-class="{'btn-success active': config[item.name].length > 0}" title="Open in DSL editor" ng-focus="specEditor.recordConfigFocus(item)"><i class="fa fa-bolt"></i></a>
                                 </span>
                             </div>
                         </div>
@@ -192,7 +298,7 @@
                             <select ng-switch-when="java.lang.Enum"
                                 ng-model="config[item.name]"
                                 ng-options="s.value as (s.description + (item.defaultValue === s.value ? ' --- default' : '')) for s in item.possibleValues"
-                                ng-focus="specEditor.recordFocus(item)"
+                                ng-focus="specEditor.recordConfigFocus(item)"
                                 class="form-control rounded-edge"
                                 name="{{item.name}}"
                                 id="{{item.name}}"></select>
@@ -201,7 +307,7 @@
                         <div ng-switch-when="map" 
                                 ng-init="expandMode = 'default'" class="collection"
                                 ng-class="{ 'open-when-unfocused': expandMode=='open' }">
-                            <p class="collection-toggle" ng-click="expandMode = cycleExpandMode(expandMode, 'map', item, state)" ng-focus="specEditor.recordFocus(item)"
+                            <p class="collection-toggle" ng-click="expandMode = cycleExpandMode(expandMode, 'map', item, state)" ng-focus="specEditor.recordConfigFocus(item)"
                                     ng-class="{ 'has-default': item.defaultValue && getObjectSize(item.defaultValue) }">
                                 <i class="fa collection-caret" ng-class="{'fa-caret-square-o-down': expandMode=='closed', 'fa-caret-square-o-up': expandMode=='open', 'caret-default': expandMode=='default' }" aria-hidden="true" title="{{expandMode=='closed' ? 'Unpin' : expandMode=='open' ? 'Close (pin)' : 'Open (pin)'}} map"></i>
                                 <span class="sr-only">{{expandMode=='closed' ? 'Unpin' : expandMode=='open' ? 'Close (pin)' : 'Open (pin)'}} map</span>
@@ -228,7 +334,7 @@
                                         <span class="input-group">
                                             <span class="main-control span-for-rounded-edge">
                                                 <input type="text" ng-model="config[item.name][mapKey]" class="form-control rounded-edge" placeholder="(empty)" 
-                                                    ng-focus="specEditor.recordFocus(item)"
+                                                    ng-focus="specEditor.recordConfigFocus(item)"
                                                     on-enter="specEditor.advanceControlInFormGroup" />
                                             </span>
                                             <span class="input-group-btn dsl-wizard-button rounded-edge" ng-if="specEditor.isDslWizardButtonAllowed(item.name, mapKey)">
@@ -240,7 +346,7 @@
                                 <li class="collection-item collection-add input-group" ng-class="{'nonempty': nonempty(config[item.name])}">
                                     <span class="main-control span-for-rounded-edge">
                                       <input ng-model="newKey" type="text" placeholder="Add property key" class="form-control rounded-edge" 
-                                        ng-focus="specEditor.recordFocus(item)"
+                                        ng-focus="specEditor.recordConfigFocus(item)"
                                         on-enter="specEditor.advanceOutToFormGroupInPanel" ng-blur="onAddMapProperty(item.name, newKey, $event); newKey = '';" required />
                                     </span>
                                     <span class="input-group-btn dsl-wizard-button rounded-edge" ng-if="specEditor.isDslWizardButtonAllowed(item.name, null, newKey)">
@@ -253,7 +359,7 @@
                         <div ng-switch-when="array" ng-switch-when-separator="|" 
                                 ng-init="expandMode = 'default'" class="collection"
                                 ng-class="{ 'open-when-unfocused': expandMode=='open' }">
-                            <p class="collection-toggle" ng-click="expandMode = cycleExpandMode(expandMode, 'map', item, state)" ng-focus="specEditor.recordFocus(item)"
+                            <p class="collection-toggle" ng-click="expandMode = cycleExpandMode(expandMode, 'map', item, state)" ng-focus="specEditor.recordConfigFocus(item)"
                                     ng-class="{ 'has-default': item.defaultValue && item.defaultValue.length }">
                                 <i class="fa collection-caret" ng-class="{'fa-caret-square-o-down': expandMode=='closed', 'fa-caret-square-o-up': expandMode=='open', 'caret-default': expandMode=='default' }" aria-hidden="true" title="{{expandMode=='closed' ? 'Unpin' : expandMode=='open' ? 'Close (pin)' : 'Open (pin)'}} list"></i>
                                 <span class="sr-only">{{expandMode=='closed' ? 'Unpin' : expandMode=='open' ? 'Close (pin)' : 'Open (pin)'}} list</span>
@@ -279,10 +385,10 @@
                                         <span class="input-group">
                                             <span class="main-control span-for-rounded-edge">
                                                 <input type="text" ng-model="config[item.name][$index]" class="form-control rounded-edge" placeholder="(empty)" 
-                                                    ng-focus="specEditor.recordFocus(item)"
+                                                    ng-focus="specEditor.recordConfigFocus(item)"
                                                     on-enter="specEditor.advanceControlInFormGroup" />
                                             </span>
-                                            <span class="input-group-btn dsl-wizard-button rounded-edge" ng-if="specEditor.isDslWizardButtonAllowed(item.name, $index)" ng-focus="specEditor.recordFocus(item)">
+                                            <span class="input-group-btn dsl-wizard-button rounded-edge" ng-if="specEditor.isDslWizardButtonAllowed(item.name, $index)" ng-focus="specEditor.recordConfigFocus(item)">
                                                 <a ui-sref="main.graphical.edit.dsl({entityId: model._id, for: item.name, index: $index})" class="btn btn-default" title="Open in DSL editor"><i class="fa fa-bolt"></i></a>
                                             </span>
                                         </span>
@@ -292,7 +398,7 @@
                                     <span class="input-group">
                                         <span class="main-control span-for-rounded-edge">
                                           <input ng-model="newItem" type="text" placeholder="Add item" class="form-control rounded-edge" auto-focus="expandMode != 'closed'" 
-                                            ng-focus="specEditor.recordFocus(item)"
+                                            ng-focus="specEditor.recordConfigFocus(item)"
                                             on-enter="specEditor.advanceOutToFormGroupInPanel" ng-blur="onAddListItem(item.name, newItem, $event, $element); newItem = '';" required />
                                         </span>
                                         <span class="input-group-btn dsl-wizard-button rounded-edge" ng-if="specEditor.isDslWizardButtonAllowed(item.name, -1, newItem)">
@@ -308,7 +414,7 @@
                                 <a ui-sref="main.graphical.edit.spec({entityId: model._id, specId: adjunct._id})"
                                             class="open-entity-spec"
                                             title="Open in spec editor"
-                                            ng-focus="specEditor.recordFocus(item)"></a>
+                                            ng-focus="specEditor.recordConfigFocus(item)"></a>
                                 <ng-include src="'blueprint-composer/component/spec-editor/adjunct.html'"></ng-include>
                             </div>
                             <a ng-if="!config[item.name][REPLACED_DSL_ENTITYSPEC]" ui-sref="main.graphical.edit.add({entityId: model._id, family: 'spec', configKey: item.name})" class="no-spec">
@@ -320,7 +426,7 @@
                             <span class="main-control span-for-rounded-edge">
                                 <textarea ng-model="config[item.name]" class="form-control rounded-edge" name="{{item.name}}" id="{{item.name}}" auto-grow
                                       placeholder="{{defined(config[item.name]) ? null : item.defaultValue=='' || item.defaultValue==null ? '(empty)' : item.defaultValue}}"
-                                      ng-focus="specEditor.recordFocus(item)" on-enter="specEditor.advanceOutToFormGroupInPanel"></textarea>
+                                      ng-focus="specEditor.recordConfigFocus(item)" on-enter="specEditor.advanceOutToFormGroupInPanel"></textarea>
                             </span>
                             <span class="input-group-btn dsl-wizard-button rounded-edge" ng-if="specEditor.isDslWizardButtonAllowed(item.name)">
                                 <a ui-sref="main.graphical.edit.dsl({entityId: model._id, for: item.name})" class="btn btn-default" title="Open in DSL editor"><i class="fa fa-bolt"></i></a>
@@ -447,6 +553,20 @@
 </script>
 
 
+<!-- PARAMETER INFO TEMPLATE :: START -->
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/parameter-info.html" defer-to-preexisting-id="true">
+    <div class="config-item-quick-info">
+        <div class="quick-info-metadata">
+            <p><i class="mini-icon fa fa-fw fa-cog"></i> <samp class="type-symbolic-name">{{item.name}}</samp>
+                <span class="config-type label-color column-for-type oneline label label-success">{{item.type}}</span></p>
+        </div>
+        <p class="quick-info-description" ng-if="item.description" ng-bind-html="specEditor.descriptionHtml(item.description)"></p>
+        <div class="quick-info-metadata config-default" ng-if="item.defaultValue"></i>Default value: <samp>{{item.defaultValue}}</samp></div>
+    </div>
+
+</script>
+<!-- PARAMETER INFO TEMPLATE :: START-->
+
 <!-- CONFIG INFO TEMPLATE :: START -->
 <script type="text/ng-template" id="blueprint-composer/component/spec-editor/config-info.html" defer-to-preexisting-id="true">
     <div class="config-item-quick-info">
diff --git a/ui-modules/blueprint-composer/app/components/util/model/entity.model.js b/ui-modules/blueprint-composer/app/components/util/model/entity.model.js
index 603eaa9..b0385b8 100644
--- a/ui-modules/blueprint-composer/app/components/util/model/entity.model.js
+++ b/ui-modules/blueprint-composer/app/components/util/model/entity.model.js
@@ -70,11 +70,11 @@
     constructor() {
         ID.set(this, Math.random().toString(36).slice(2));
         CONFIG.set(this, new Map());
-        PARAMETERS.set(this, new Map());
+        PARAMETERS.set(this, []);
         METADATA.set(this, new Map());
         ENRICHERS.set(this, new Map());
         POLICIES.set(this, new Map());
-        CHILDREN.set(this, new Array());
+        CHILDREN.set(this, []);
         MISC_DATA.set(this, new Map());
         MISC_DATA.get(this).set('issues', []);
         this.family = EntityFamily.ENTITY.id;
@@ -484,7 +484,7 @@
      * @returns {boolean}
      */
     hasParameters() {
-        return PARAMETERS.get(this).size > 0;
+        return PARAMETERS.get(this).length > 0;
     }
 
     /**
@@ -616,8 +616,7 @@
 }
 
 function addParameter(param) {
-    let key = param.name;
-    PARAMETERS.get(this).set(key, param);
+    PARAMETERS.get(this).push(param);
     this.touch();
     return this;
 }
@@ -646,12 +645,19 @@
 
 /**
  * Remove an entry from brooklyn.parameters
- * @param {string} key
+ * @param {string} name
  * @returns {Entity}
  */
-function removeParameter(key) {
-    PARAMETERS.get(this).delete(key);
-    this.touch();
+function removeParameter(name) {
+    if (this.hasParameters()) {
+        let paramIndex = PARAMETERS.get(this)
+            .filter(e => e.name === name)
+            .map(e => PARAMETERS.get(this).indexOf(e));
+        if (paramIndex.length > 0) {
+            let removed = PARAMETERS.get(this).splice(paramIndex[0], 1);
+            this.touch();
+        }
+    }
     return this;
 }
 
@@ -885,11 +891,11 @@
     ID.set(this, Math.random().toString(36).slice(2));
     this.removeLocation();
     CONFIG.set(this, new Map());
-    PARAMETERS.set(this, new Map());
+    PARAMETERS.set(this, []);
     METADATA.set(this, new Map());
     ENRICHERS.set(this, new Map());
     POLICIES.set(this, new Map());
-    CHILDREN.set(this, new Array());
+    CHILDREN.set(this, []);
     MISC_DATA.set(this, new Map());
     MISC_DATA.get(this).set('issues', []);
     this.family = EntityFamily.ENTITY.id;
@@ -984,10 +990,10 @@
     if (!Array.isArray(incomingModel)) {
         throw new Error('Model parse error ... cannot add children as it must be an array')
     }
-    var children = new Array();
+    let children = [];
 
     incomingModel.reduce((self, child)=> {
-        var childEntity = new Entity();
+        let childEntity = new Entity();
         childEntity.setEntityFromJson(child);
         childEntity.parent = self;
         children.push(childEntity);
@@ -1003,7 +1009,7 @@
  */
 function setConfigFromJson(incomingModel) {
     CONFIG.get(this).clear();
-    var self = this;
+    let self = this;
     Object.keys(incomingModel).forEach((key)=>(self.addConfig(key, incomingModel[key])));
     this.touch();
 }
@@ -1016,8 +1022,9 @@
     if (!Array.isArray(incomingModel)) {
         throw new Error('Model parse error ... cannot add parameters as it must be an array')
     }
-    PARAMETERS.get(this).clear();
-    var self = this;
+    PARAMETERS.set(this, []);
+
+    let self = this;
     incomingModel.map((param)=> {
         self.addParameter(param);
     });
@@ -1027,7 +1034,7 @@
 
 function setMetadataFromJson(incomingModel) {
     METADATA.get(this).clear();
-    var self = this;
+    let self = this;
     Object.keys(incomingModel).forEach((key)=> (self.addMetadata(key, incomingModel[key])));
     this.touch();
 }
@@ -1083,7 +1090,7 @@
 }
 
 function getParametersAsArray() {
-    return Array.from(PARAMETERS.get(this).values());
+    return PARAMETERS.get(this);
 }
 
 /* "cleaning" here means:  Dsl objects are toStringed, to the given depth (or infinite if depth<0);