소스 검색

HPCC-9034 Add Package Maps GUI to EclWatch

This fix adds Package Maps link to EclWatch navigation section. From this
link, EclWatch displays a list of all Package Maps and multiple controls
for Package Maps. The list may be filtered using target, process, etc.
The controls may be used to add new Package Maps, activate/deactivate
Package Maps, and delete Package Maps. Details of each Package Map may be
viewed using Open button.

Signed-off-by: Kevin Wang kevin.wang@lexisnexis.com
Kevin Wang 11 년 전
부모
커밋
858b11c95e

+ 9 - 9
esp/bindings/http/platform/httpbinding.cpp

@@ -1526,7 +1526,7 @@ int EspHttpBinding::onGetRespSampleXml(IEspContext &ctx, CHttpRequest* request,
 
 int EspHttpBinding::onStartUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method)
 {
-    StringArray fileNames;
+    StringArray fileNames, files;
     Owned<IMultiException> me = MakeMultiException("FileSpray::UploadFile()");
     try
     {
@@ -1536,12 +1536,12 @@ int EspHttpBinding::onStartUpload(IEspContext &ctx, CHttpRequest* request, CHttp
         StringBuffer netAddress, path;
         request->getParameter("NetAddress", netAddress);
         request->getParameter("Path", path);
+        if (((netAddress.length() < 1) || (path.length() < 1)))
+            request->readUploadFileContent(fileNames, files);
+        else
+            request->readContentToFiles(netAddress, path, fileNames);
 
-        if ((netAddress.length() < 1) || (path.length() < 1))
-            throw MakeStringException(-1, "Upload destination not specified.");
-
-        request->readContentToFiles(netAddress, path, fileNames);
-        return onFinishUpload(ctx, request, response, serv, method, fileNames, NULL);
+        return onFinishUpload(ctx, request, response, serv, method, fileNames, files, NULL);
     }
     catch (IException* e)
     {
@@ -1551,10 +1551,11 @@ int EspHttpBinding::onStartUpload(IEspContext &ctx, CHttpRequest* request, CHttp
     {
         me->append(*MakeStringExceptionDirect(-1, "Unknown Exception"));
     }
-    return onFinishUpload(ctx, request, response, serv, method, fileNames, me);
+    return onFinishUpload(ctx, request, response, serv, method, fileNames, files, me);
 }
 
-int EspHttpBinding::onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method, StringArray& fileNames, IMultiException *me)
+int EspHttpBinding::onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method,
+                                   StringArray& fileNames, StringArray& files, IMultiException *me)
 {
     response->setContentType("text/html; charset=UTF-8");
     StringBuffer content(
@@ -1583,7 +1584,6 @@ int EspHttpBinding::onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHtt
     return 0;
 }
 
-
 int EspHttpBinding::getWsdlMessages(IEspContext &context, CHttpRequest *request, StringBuffer &content, const char *service, const char *method, bool mda)
 {
     bool allMethods = (method==NULL || !*method);

+ 4 - 2
esp/bindings/http/platform/httpbinding.hpp

@@ -94,7 +94,8 @@ interface IEspHttpBinding
     virtual int onGetReqSampleXml(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method)=0;
     virtual int onGetRespSampleXml(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method)=0;
     virtual int onStartUpload(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method)=0;
-    virtual int onFinishUpload(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method, StringArray& fileNames, IMultiException *me)=0;
+    virtual int onFinishUpload(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method,
+        StringArray& fileNames, StringArray& files, IMultiException *me)=0;
 };
 
 typedef MapStringTo<int> wsdlIncludedTable;
@@ -251,7 +252,8 @@ public:
     virtual int onGetRespSampleXml(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method);
 
     virtual int onStartUpload(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method);
-    virtual int onFinishUpload(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method, StringArray& fileNames, IMultiException *me);
+    virtual int onFinishUpload(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method,
+        StringArray& fileNames, StringArray& files, IMultiException *me);
 
 //interface IEspWsdlSections
     StringBuffer & getServiceName(StringBuffer & resp){return resp;}

+ 42 - 0
esp/bindings/http/platform/httptransport.cpp

@@ -1928,6 +1928,48 @@ IFile* CHttpRequest::createUploadFile(StringBuffer netAddress, const char* fileP
     return createIFile(rfn);
 }
 
+void CHttpRequest::readUploadFileContent(StringArray& fileNames, StringArray& files)
+{
+    Owned<CMimeMultiPart> multipart = new CMimeMultiPart("1.0", m_content_type.get(), "", "", "");
+    multipart->parseContentType(m_content_type.get());
+
+    MemoryBuffer fileContent, moreContent;
+    __int64 bytesNotRead = m_content_length64;
+    while (1)
+    {
+        StringBuffer fileName, content;
+        if (!readUploadFileName(multipart, fileName, fileContent, bytesNotRead))
+        {
+            DBGLOG("No file name found for upload");
+            return;
+        }
+
+        bool foundAnotherFile = false;
+        while (1)
+        {
+            foundAnotherFile = multipart->separateMultiParts(fileContent, moreContent, bytesNotRead);
+            if (fileContent.length() > 0)
+                content.append(fileContent.length(), fileContent.toByteArray());
+
+            fileContent.clear();
+            if (moreContent.length() > 0)
+            {
+                fileContent.append(moreContent.length(), (void*) moreContent.toByteArray());
+                moreContent.clear();
+            }
+
+            if(foundAnotherFile || (bytesNotRead <= 0) || !readContentToBuffer(fileContent, bytesNotRead))
+                break;
+        }
+
+        fileNames.append(fileName);
+        files.append(content);
+        if (!foundAnotherFile)
+            break;
+    }
+    return;
+}
+
 int CHttpRequest::readContentToFiles(StringBuffer netAddress, StringBuffer path, StringArray& fileNames)
 {
     const char* contentType = m_content_type.get();

+ 1 - 0
esp/bindings/http/platform/httptransport.ipp

@@ -360,6 +360,7 @@ public:
     bool readUploadFileName(CMimeMultiPart* mimemultipart, StringBuffer& fileName, MemoryBuffer& contentBuffer, __int64& bytesNotRead);
     IFile* createUploadFile(StringBuffer netAddress, const char* filePath, StringBuffer& fileName);
     virtual int readContentToFiles(StringBuffer netAddress, StringBuffer path, StringArray& fileNames);
+    virtual void readUploadFileContent(StringArray& fileNames, StringArray& files);
 };
 
 class CHttpResponse : public CHttpMessage

+ 157 - 0
esp/files/scripts/ESPPackageProcess.js

@@ -0,0 +1,157 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2013 HPCC Systems.
+#
+#    Licensed 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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/_base/lang",
+    "dojo/_base/Deferred",
+    "dojo/data/ObjectStore",
+    "dojo/store/util/QueryResults",
+    "dojo/store/Observable",
+
+    "hpcc/WsPackageMaps",
+    "hpcc/ESPUtil"
+], function (declare, arrayUtil, lang, Deferred,
+    ObjectStore, QueryResults, Observable,
+    WsPackageMaps, ESPUtil) {
+
+    var _packageMaps = {};
+    var Store = declare(null, {
+        idProperty: "Id",
+
+        _watched: {},
+
+        constructor: function (options) {
+            declare.safeMixin(this, options);
+        },
+
+        getIdentity: function (object) {
+            return object[this.idProperty];
+        },
+
+        get: function (id) {
+            if (!_packageMaps[id]) {
+                _packageMaps[id] = new packageMap({
+                    Id: id
+                });
+            }
+            return _packageMaps[id];
+        },
+
+        sortPackageMaps: function (packageMaps, sortIn) {
+            packageMaps.sort(function(a, b){
+                var vA = a.Id;
+                var vB = b.Id;
+                if (sortIn.attribute == 'Target') {
+                    vA = a.Target;
+                    vB = b.Target;
+                }
+                else if (sortIn.attribute == 'Process') {
+                    vA = a.Process;
+                    vB = b.Process;
+                }
+                else if (sortIn.attribute == 'Description') {
+                    vA = a.Description;
+                    vB = b.Description;
+                }
+                else if (sortIn.attribute == 'Active') {
+                    vA = a.Active;
+                    vB = b.Active;
+                }
+                if (sortIn.descending) {
+                    if (vA < vB) //sort string ascending
+                        return 1;
+                    if (vA > vB)
+                        return -1;
+                }
+                else {
+                    if (vA < vB)
+                        return -1;
+                    if (vA > vB)
+                        return 1;
+                }
+                return 0 //default return value (no sorting)
+            })
+            return packageMaps;
+        },
+
+        query: function (query, options) {
+            var request = {};
+            lang.mixin(request, options.query);
+            if (options.query.Target)
+                request['Target'] = options.query.Target;
+            if (options.query.Process)
+                request['Process'] = options.query.Process;
+            if (options.query.ProcessFilter)
+                request['ProcessFilter'] = options.query.ProcessFilter;
+
+            var results = WsPackageMaps.PackageMapQuery({
+                request: request
+            });
+
+            var deferredResults = new Deferred();
+            deferredResults.total = results.then(function (response) {
+                if (lang.exists("ListPackagesResponse.NumPackages", response)) {
+                    return response.ListPackagesResponse.NumPackages;
+                }
+                return 0;
+            });
+            var context = this;
+            Deferred.when(results, function (response) {
+                var packageMaps = [];
+                for (key in context._watched) {
+                    context._watched[key].unwatch();
+                }
+                this._watched = {};
+                if (lang.exists("ListPackagesResponse.PackageMapList.PackageListMapData", response)) {
+                    arrayUtil.forEach(response.ListPackagesResponse.PackageMapList.PackageListMapData, function (item, index) {
+                        var packageMap = context.get(item.Id);
+                        packageMap.updateData(item);
+                        packageMaps.push(packageMap);
+                        context._watched[packageMap.Id] = packageMap.watch("changedCount", function (name, oldValue, newValue) {
+                            if (oldValue !== newValue) {
+                                context.notify(packageMap, packageMap.Id);
+                            }
+                        });
+                    });
+                    if (options.sort) {
+                        packageMaps = context.sortPackageMaps(packageMaps, options.sort[0]);
+                    }
+                }
+                deferredResults.resolve(packageMaps);
+            });
+
+            return QueryResults(deferredResults);
+        }
+    });
+
+    var packageMap = declare([ESPUtil.Singleton], {
+        constructor: function (args) {
+            this.inherited(arguments);
+            declare.safeMixin(this, args);
+            this.packageMap = this;
+        }
+    });
+
+    return {
+        CreatePackageMapQueryObjectStore: function (options) {
+            var store = new Store(options);
+            store = Observable(store);
+            var objStore = new ObjectStore({ objectStore: store });
+            return objStore;
+        }
+    };
+});

+ 201 - 0
esp/files/scripts/PackageMapDetailsWidget.js

@@ -0,0 +1,201 @@
+/*##############################################################################
+#   HPCC SYSTEMS software Copyright (C) 2013 HPCC Systems.
+#
+#   Licensed 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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/dom",
+    "dojo/dom-attr",
+    "dojo/dom-class",
+    "dojo/topic",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/registry",
+
+    "hpcc/WsPackageMaps",
+    "hpcc/PackageSourceWidget",
+
+    "dojo/text!../templates/PackageMapDetailsWidget.html",
+
+    "dijit/layout/BorderContainer",
+    "dijit/layout/TabContainer",
+    "dijit/layout/ContentPane",
+    "dijit/form/Button",
+    "dijit/Toolbar",
+    "dijit/TooltipDialog",
+    "dijit/TitlePane"
+], function (declare, dom, domAttr, domClass, topic,
+    _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, registry,
+    WsPackageMaps, PackageSourceWidget, template) {
+    return declare("PackageMapDetailsWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "PackageMapDetailsWidget",
+        borderContainer: null,
+        tabContainer: null,
+        validateWidget: null,
+        validateWidgetLoaded: false,
+        xmlWidget: null,
+        xmlWidgetLoaded: false,
+
+        initalized: false,
+        tabId: "",
+        packageMap: "",
+        target: "",
+        process: "",
+        active: false,
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
+            this.tabContainer = registry.byId(this.id + "TabContainer");
+            this.validateWidget = registry.byId(this.id + "Validate");
+            this.xmlWidget = registry.byId(this.id + "XML");
+
+            var context = this;
+            this.tabContainer.watch("selectedChildWidget", function (name, oval, nval) {
+                if (nval.id == context.id + "Validate" && !context.validateWidgetLoaded) {
+                    context.validateWidgetLoaded = true;
+                    context.validateWidget.init({
+                        target: context.target,
+                        process: context.process,
+                        packageMap: context.packageMap
+                    });
+                } else if (nval.id == context.id + "XML" && !context.xmlWidgetLoaded) {
+                    context.xmlWidgetLoaded = true;
+                    context.xmlWidget.init({
+                        target: context.target,
+                        process: context.process,
+                        packageMap: context.packageMap
+                    });
+                }
+            });
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.borderContainer.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        init: function (params) {
+            if (this.initalized)
+                return;
+            this.initalized = true;
+            this.tabId = params.tabId;
+            this.packageMap = params.packageMap;
+            this.target = params.target;
+            this.process = params.process;
+            this.active = params.active;
+            if (params.packageMap) {
+                registry.byId(this.id + "Summary").set("title", params.packageMap);
+                domAttr.set(this.id + "PMID", "innerHTML", params.packageMap);
+                domAttr.set(this.id + "Target", "value", params.target);
+                domAttr.set(this.id + "Process", "value", params.process);
+                if (params.active == true)
+                    domClass.add(this.id + "StateIdImage", "iconRunning");
+                else
+                    domClass.add(this.id + "StateIdImage", "iconArchived");
+            }
+            this.refreshActionState();
+        },
+
+        refreshActionState: function () {
+            registry.byId(this.id + "Activate").set("disabled", this.active);
+            registry.byId(this.id + "Deactivate").set("disabled", !this.active);
+            domAttr.set(this.id + "StateIdImage", "title", this.active? "Active":"Not active");
+        },
+
+        showErrorMessage: function (message) {
+            dojo.publish("hpcc/brToaster", {
+                message: message,
+                type: "error",
+                duration: -1
+            });
+        },
+
+        showErrors: function (errMsg, errStack) {
+            var message = "Unknown Error";
+            if (errMsg != '')
+                message = "<h3>" + errMsg + "</h3>";
+            if (errStack != '')
+                message += "<p>" + errStack + "</p>";
+            this.showErrorMessage(message);
+        },
+
+        _onActivate: function (event) {
+            var context = this;
+            var packageMaps = [];
+            packageMaps[0] = {Target:this.target,
+                Process:this.process,Id:this.packageMap};
+
+            WsPackageMaps.activatePackageMap(packageMaps, {
+                load: function (response) {
+                    domClass.replace(context.id + "StateIdImage", "iconRunning");
+                    context.active = true;
+                    context.refreshActionState();
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                }
+            });
+        },
+        _onDeactivate: function (event) {
+            var context = this;
+            var packageMaps = [];
+            packageMaps[0] = {Target:this.target,
+                Process:this.process,Id:this.packageMap};
+
+            WsPackageMaps.deactivatePackageMap(packageMaps, {
+                load: function (response) {
+                    domClass.replace(context.id + "StateIdImage", "iconArchived");
+                    context.active = false;
+                    context.refreshActionState();
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                }
+            });
+        },
+        _onDelete: function (event) {
+            if (confirm('Delete selected package?')) {
+                var context = this;
+                var packageMaps = [];
+                packageMaps[0] = {Target:this.target,
+                    Process:this.process,Id:this.packageMap};
+
+                WsPackageMaps.deletePackageMap(packageMaps, {
+                    load: function (response) {
+                        topic.publish("packageMapDeleted", context.tabId);
+                    },
+                    error: function (errMsg, errStack) {
+                        context.showErrors(errMsg, errStack);
+                    }
+                });
+            }
+        }
+    });
+});

+ 533 - 0
esp/files/scripts/PackageMapQueryWidget.js

@@ -0,0 +1,533 @@
+/*##############################################################################
+#   HPCC SYSTEMS software Copyright (C) 2013 HPCC Systems.
+#
+#   Licensed 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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/_base/array",
+    "dojo/dom",
+    "dojo/dom-construct",
+    "dojo/dom-form",
+    "dojo/data/ObjectStore",
+    "dojo/on",
+    "dojo/topic",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/registry",
+    "dojox/form/Uploader",
+    "dojox/form/uploader/FileList",
+    "dojox/form/uploader/plugins/Flash",
+    "dojox/grid/EnhancedGrid",
+    "dojox/grid/enhanced/plugins/Pagination",
+    "dojox/grid/enhanced/plugins/IndirectSelection",
+    "dojo/data/ItemFileWriteStore",
+
+    "hpcc/PackageMapDetailsWidget",
+    "hpcc/PackageMapValidateWidget",
+    "hpcc/WsPackageMaps",
+    "hpcc/ESPPackageProcess",
+    "hpcc/SFDetailsWidget",
+
+    "dojo/text!../templates/PackageMapQueryWidget.html",
+
+    "dojox/layout/TableContainer",
+    "dijit/layout/BorderContainer",
+    "dijit/layout/TabContainer",
+    "dijit/layout/ContentPane",
+    "dijit/form/Button",
+    "dijit/form/DropDownButton",
+    "dijit/form/Select",
+    "dijit/Toolbar",
+    "dijit/TooltipDialog"
+], function (declare, lang, arrayUtil, dom, domConstruct, domForm, ObjectStore, on, topic,
+    _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, registry,
+    Uploader, FileUploader, Flash, EnhancedGrid, Pagination, IndirectSelection, ItemFileWriteStore,
+    PackageMapDetailsWidget, PackageMapValidateWidget,
+    WsPackageMaps, ESPPackageProcess, SFDetailsWidget,
+    template) {
+    return declare("PackageMapQueryWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "PackageMapQueryWidget",
+        packagesTab: null,
+        packagesGrid: null,
+        tabMap: [],
+        targets: null,
+        processesToList: new Array(),
+        processesToAdd: new Array(),
+        targetSelected: '',
+        processSelected: '',
+        processFilters: null,
+        addPackageMapDialog: null,
+        validateTab: null,
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
+            this.tabContainer = registry.byId(this.id + "TabContainer");
+            this.packagesTab = registry.byId(this.id + "Packages");
+            this.packagesGrid = registry.byId(this.id + "PackagesGrid");
+            //this.packagesGrid.canSort = function(col){return false;};
+            this.targetSelect = registry.byId(this.id + "TargetSelect");
+            this.processSelect = registry.byId(this.id + "ProcessSelect");
+            //this.processFilterSelect = registry.byId(this.id + "ProcessFilterSelect");
+            this.addPackageMapDialog = registry.byId(this.id+"AddProcessMapDialog");
+            this.addPackageMapTargetSelect = registry.byId(this.id + "AddProcessMapTargetSelect");
+            this.addPackageMapProcessSelect = registry.byId(this.id + "AddProcessMapProcessSelect");
+
+            var context = this;
+            this.tabContainer.watch("selectedChildWidget", function (name, oval, nval) {
+                if ((nval.id != context.id + "Packages") && (!nval.initalized)) {
+                    nval.init(nval.params);
+                }
+                context.selectedTab = nval;
+            });
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+            this.refreshActionState();
+            this.getSelections();
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.borderContainer.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        destroy: function (args) {
+            this.inherited(arguments);
+        },
+
+        onRowDblClick: function (item) {
+            var tab = this.showPackageMapDetails(this.id + "_" + item.Id, {
+                    target: item.Target,
+                    process: item.Process,
+                    active: item.Active,
+                    packageMap: item.Id
+                });
+            this.tabContainer.selectChild(tab);
+        },
+
+        _onChangeTarget: function (event) {
+            this.updateProcessSelections(this.processSelect, this.processesToList, this.targetSelect.getValue());
+        },
+
+        _onChangeAddProcessMapTarget: function (event) {
+            this.updateProcessSelections(this.addPackageMapProcessSelect, this.processesToAdd, this.addPackageMapTargetSelect.getValue());
+        },
+
+        _onRefresh: function (event) {
+            this.packagesGrid.rowSelectCell.toggleAllSelection(false);
+            this.refreshGrid();
+        },
+
+        _onOpen: function (event) {
+            var selections = this.packagesGrid.selection.getSelected();
+            var firstTab = null;
+            for (var i = selections.length - 1; i >= 0; --i) {
+                var tab = this.showPackageMapDetails(this.id + "_" + selections[i].Id, {
+                    target: selections[i].Target,
+                    process: selections[i].Process,
+                    active: selections[i].Active,
+                    packageMap: selections[i].Id
+                });
+                if (i == 0) {
+                    firstTab = tab;
+                }
+            }
+            if (firstTab) {
+                this.tabContainer.selectChild(firstTab, true);
+            }
+        },
+        _onAdd: function (event) {
+            this.initAddProcessMapInput();
+            this.addPackageMapDialog.show();
+
+            var context = this;
+            var addPackageMapUploader = registry.byId(this.id+"AddProcessMapFileUploader");
+            dojo.connect(addPackageMapUploader, "onComplete", this, function(e) {
+                registry.byId(this.id+"AddProcessMapDialogSubmit").set('disabled', false);
+                return context.addPackageMapCallback();
+            });
+            dojo.connect(addPackageMapUploader, "onBegin", this, function(e) {
+                registry.byId(this.id+"AddProcessMapDialogSubmit").set('disabled', true);
+                return;
+            });
+            var addPackageMapSubmitButton = registry.byId(this.id+"AddProcessMapDialogSubmit");
+            dojo.connect(addPackageMapSubmitButton, "onClick", this, function(e) {
+                return context._onAddPackageMapSubmit();
+            });
+            var addPackageMapCloseButton = registry.byId(this.id+"AddProcessMapDialogClose");
+            dojo.connect(addPackageMapCloseButton, "onClick", this, function(e) {
+                this.addPackageMapDialog.onCancel();
+            });
+        },
+        initAddProcessMapInput: function () {
+            var defaultTarget = null;
+            for (var i = 0; i < this.targets.length; ++i) {
+                var target = this.targets[i];
+                if (target.Type == 'roxie') {//only roxie has package map for now.
+                    this.addPackageMapTargetSelect.options.push({label: target.Name, value: target.Name});
+                    if (defaultTarget == null)
+                        defaultTarget = target;
+                }
+            }
+            if (defaultTarget != null) {
+                this.addPackageMapTargetSelect.set("value", defaultTarget.Name);
+                if (defaultTarget.Processes != undefined)
+                    this.addProcessSelections(this.addPackageMapProcessSelect, this.processesToAdd, defaultTarget.Processes.Item);
+            }
+            registry.byId(this.id+"AddProcessMapId").set('value', '');
+            registry.byId(this.id+"AddProcessMapDaliIP").set('value', '');
+            registry.byId(this.id+"AddProcessMapActivate").set('checked', 'checked');
+            registry.byId(this.id+"AddProcessMapOverWrite").set('checked', '');
+            registry.byId(this.id+"AddProcessMapFileUploader").reset();
+            registry.byId(this.id+"AddProcessMapFileUploader").set('url', '');
+            registry.byId(this.id+"AddProcessMapForm").set('action', '');
+            registry.byId(this.id+"AddProcessMapDialogSubmit").set('disabled', true);
+        },
+        _onAddProcessMapIdKeyUp: function () {
+            this._onCheckAddProcessMapInput();
+        },
+        _onCheckAddProcessMapInput: function () {
+            var id = registry.byId(this.id+"AddProcessMapId").get('value');
+            var files = registry.byId(this.id+"AddProcessMapFileUploader").getFileList();
+            if (files.length > 1) {
+                alert('Only one package file allowed');
+                return;
+            }
+            var fileName = '';
+            if (files.length > 0)
+                fileName = files[0].name;
+            if ((fileName != '') && (id == '')) {
+                registry.byId(this.id+"AddProcessMapId").set('value', fileName);
+                registry.byId(this.id+"AddProcessMapDialogSubmit").set('disabled', false);
+            } else if ((id == '') || (files.length < 1))
+                registry.byId(this.id+"AddProcessMapDialogSubmit").set('disabled', true);
+            else
+                registry.byId(this.id+"AddProcessMapDialogSubmit").set('disabled', false);
+        },
+        _onAddPackageMapSubmit: function () {
+            var target = this.addPackageMapTargetSelect.getValue();
+            var id = registry.byId(this.id+"AddProcessMapId").get('value');
+            //var process = registry.byId(this.id+"AddProcessMapProcess").get('value');
+            var process = this.addPackageMapProcessSelect.getValue();
+            var daliIp = registry.byId(this.id+"AddProcessMapDaliIP").get('value');
+            var activate = registry.byId(this.id+"AddProcessMapActivate").get('checked');
+            var overwrite = registry.byId(this.id+"AddProcessMapOverWrite").get('checked');
+            if ((id == '') || (target == ''))
+                return false;
+            if  ((process == '') || (process == 'ANY'))
+                process = '*';
+
+            var action = "/WsPackageProcess/AddPackage?upload_&PackageMap="+id+"&Target="+target;
+            if (process != '')
+                action += "&Process="+process;
+            if (daliIp != '')
+                action += "&DaliIp="+daliIp;
+            if (activate)
+                action += "&Activate=1";
+            else
+                action += "&Activate=0";
+            if (overwrite)
+                action += "&OverWrite=1";
+            else
+                action += "&OverWrite=0";
+            var theForm = registry.byId(this.id+"AddProcessMapForm");
+            if (theForm == undefined)
+                return false;
+            theForm.set('action', action);
+            return true;
+        },
+        _onDelete: function (event) {
+            if (confirm('Delete selected packages?')) {
+                var context = this;
+                WsPackageMaps.deletePackageMap(this.packagesGrid.selection.getSelected(), {
+                    load: function (response) {
+                        context.packagesGrid.rowSelectCell.toggleAllSelection(false);
+                        context.refreshGrid(response);
+                    },
+                    error: function (errMsg, errStack) {
+                        context.showErrors(errMsg, errStack);
+                    }
+                });
+            }
+        },
+        _onActivate: function (event) {
+            var context = this;
+            WsPackageMaps.activatePackageMap(this.packagesGrid.selection.getSelected(), {
+                load: function (response) {
+                    context.packagesGrid.rowSelectCell.toggleAllSelection(false);
+                    context.refreshGrid();
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                }
+            });
+        },
+        _onDeactivate: function (event) {
+            var context = this;
+            WsPackageMaps.deactivatePackageMap(this.packagesGrid.selection.getSelected(), {
+                load: function (response) {
+                    context.packagesGrid.rowSelectCell.toggleAllSelection(false);
+                    context.refreshGrid();
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                }
+            });
+        },
+
+        showErrorMessage: function (message) {
+            dojo.publish("hpcc/brToaster", {
+                message: message,
+                type: "error",
+                duration: -1
+            });
+        },
+
+        showErrors: function (errMsg, errStack) {
+            var message = "Unknown Error";
+            if (errMsg != '')
+                message = "<h3>" + errMsg + "</h3>";
+            if (errStack != '')
+                message += "<p>" + errStack + "</p>";
+            this.showErrorMessage(message);
+        },
+
+        getSelections: function () {
+            this.targets = new Array();
+            ///this.processes = new Array();
+            this.processFilters = new Array();
+
+            var context = this;
+            WsPackageMaps.GetPackageMapSelectOptions({
+                    includeTargets: true,
+                    IncludeProcesses: true,
+                    IncludeProcessFilters: true
+                }, {
+                load: function (response) {
+                    context.targetSelect.options.push({label: 'ANY', value: '' });
+                    context.processSelect.options.push({label: 'ANY', value: '' });
+                    if (lang.exists("Targets.TargetData", response)) {
+                        context.targets = response.Targets.TargetData;
+                        context.initSelections();
+                    }
+                    context.targetSelect.set("value", '');
+                    context.processSelect.set("value", '');
+                    if (lang.exists("ProcessFilters.Item", response)) {
+                        context.processFilters = response.ProcessFilters.Item;
+                    //    context.setSelections(context.processFilterSelect, context.processFilters, '*');
+                    }
+                    context.initPackagesGrid();
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                }
+            });
+        },
+
+        addProcessSelections: function (processSelect, processes, processData) {
+            for (var i = 0; i < processData.length; ++i) {
+                var process = processData[i];
+                if ((processes != null) && (processes.indexOf(process) != -1))
+                    continue;
+                processes.push(process);
+                processSelect.options.push({label: process, value: process});
+            }
+        },
+
+        updateProcessSelections: function (processSelect, processes, targetName) {
+            var options = processSelect.getOptions();
+            for (var ii = 0; ii < options.length; ++ii) {
+                var value = options[ii].value;
+                processSelect.removeOption(value);
+            }
+            ///processSelect.removeOption(processSelect.getOptions());
+            processSelect.options.push({label: 'ANY', value: '' });
+            processes.length = 0;
+            for (var i = 0; i < this.targets.length; ++i) {
+                var target = this.targets[i];
+                if ((target.Processes != undefined) && ((targetName == '') || (targetName == target.Name)))
+                    this.addProcessSelections(processSelect, processes, target.Processes.Item);
+            }
+            processSelect.set("value", '');
+        },
+
+        initSelections: function () {
+            if (this.targets.length < 1)
+                return;
+
+            for (var i = 0; i < this.targets.length; ++i) {
+                var target = this.targets[i];
+                this.targetSelect.options.push({label: target.Name, value: target.Name});
+                if (target.Processes != undefined)
+                    this.addProcessSelections(this.processSelect, this.processesToList, target.Processes.Item);
+            }
+            if (this.validateTab != null)
+                this.validateTab.initSelections(this.targets);
+        },
+
+        init: function (params) {
+            if (this.initalized)
+                return;
+
+            this.initalized = true;
+
+            this.validateTab = new PackageMapValidateWidget({
+                id: this.id + "_ValidatePackageMap",
+                title: 'Validate PackageMap',
+                params: params
+            });
+            //this.tabMap[this.id + "_ValidatePackageMap"] = this.validateTab;
+            this.tabContainer.addChild(this.validateTab, 1);
+
+            this.tabContainer.selectChild(this.packagesTab);
+        },
+
+        initPackagesGrid: function() {
+            this.packagesGrid.setStructure([
+                { name: "Package Map", field: "Id", width: "40%" },
+                { name: "Target", field: "Target", width: "15%" },
+                { name: "Process Filter", field: "Process", width: "15%" },
+                {
+                    name: "Active",
+                    field: "Active",
+                    width: "10%",
+                    formatter: function (active) {
+                        if (active == true) {
+                            return "A";
+                        }
+                        return "";
+                    }
+                },
+                { name: "Description", field: "Description", width: "20%" }
+            ]);
+            var objStore = ESPPackageProcess.CreatePackageMapQueryObjectStore();
+            this.packagesGrid.setStore(objStore);
+            this.packagesGrid.setQuery(this.getFilter());
+
+            var context = this;
+            this.packagesGrid.on("RowDblClick", function (evt) {
+                if (context.onRowDblClick) {
+                    var idx = evt.rowIndex;
+                    var item = this.getItem(idx);
+                    context.onRowDblClick(item);
+                }
+            }, true);
+
+            dojo.connect(this.packagesGrid.selection, 'onSelected', function (idx) {
+                context.refreshActionState();
+            });
+            dojo.connect(this.packagesGrid.selection, 'onDeselected', function (idx) {
+                context.refreshActionState();
+            });
+
+            this.packagesGrid.startup();
+        },
+
+        getFilter: function () {
+            this.targetSelected  = this.targetSelect.getValue();
+            this.processSelected  = this.processSelect.getValue();
+            //var processFilterSelected  = this.processFilterSelect.getValue();
+            var processFilterSelected  = "*";
+            if (this.targetSelected == " ")
+                this.targetSelected = "";
+            if (this.processSelected == " ")
+                this.processSelected = "";
+            if (processFilterSelected == "")
+                processFilterSelected = "*";
+            return {Target: this.targetSelected, Process: this.processSelected, ProcessFilter: processFilterSelected};
+        },
+
+        refreshGrid: function (args) {
+            this.packagesGrid.setQuery(this.getFilter());
+            var context = this;
+            setTimeout(function () {
+                context.refreshActionState()
+            }, 200);
+        },
+
+        refreshActionState: function () {
+            var selection = this.packagesGrid.selection.getSelected();
+            var hasSelection = (selection.length > 0);
+            registry.byId(this.id + "Open").set("disabled", !hasSelection);
+            registry.byId(this.id + "Delete").set("disabled", !hasSelection);
+            registry.byId(this.id + "Activate").set("disabled", selection.length != 1);
+            registry.byId(this.id + "Deactivate").set("disabled", selection.length != 1);
+        },
+
+        showPackageMapDetails: function (id, params) {
+            var obj = id.split(".");
+            id = obj.join("");
+            params.tabId = id;
+
+            var retVal = this.tabMap[id];
+            if (retVal)
+                return retVal;
+
+            var context = this;
+            retVal = new PackageMapDetailsWidget({
+                id: id,
+                title: params.packageMap,
+                closable: true,
+                onClose: function () {
+                    delete context.tabMap[id];
+                    return true;
+                },
+                params: params
+            });
+
+            this.tabMap[id] = retVal;
+            this.tabContainer.addChild(retVal, 2);
+
+            var handle = topic.subscribe("packageMapDeleted", function(tabId){
+                context.packageMapDeleted(tabId);
+                handle.remove();
+            });
+
+            return retVal;
+        },
+
+        packageMapDeleted: function (tabId) {
+            if (this.tabMap[tabId] == null)
+                return;
+            this.tabContainer.removeChild(this.tabMap[tabId]);
+            this.tabMap[tabId].destroyRecursive();
+            delete this.tabMap[tabId];
+
+            this.tabContainer.selectChild(this.packagesTab);
+            this.packagesGrid.rowSelectCell.toggleAllSelection(false);
+            this.refreshGrid();
+        },
+
+        addPackageMapCallback: function (event) {
+            //var processFilter = this.addPackageMapProcessSelect.getValue();
+            this.addPackageMapDialog.onCancel();
+            this.packagesGrid.rowSelectCell.toggleAllSelection(false);
+            this.refreshGrid();
+        }
+    });
+});

+ 208 - 0
esp/files/scripts/PackageMapValidateWidget.js

@@ -0,0 +1,208 @@
+/*##############################################################################
+#   HPCC SYSTEMS software Copyright (C) 2013 HPCC Systems.
+#
+#   Licensed 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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/dom",
+    "dojo/dom-attr",
+    "dojo/dom-class",
+    "dojo/topic",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/registry",
+
+    "hpcc/WsPackageMaps",
+
+    "dojo/text!../templates/PackageMapValidateWidget.html",
+
+    "dijit/form/Form",
+    "dijit/form/Button",
+    "dijit/form/RadioButton",
+    "dijit/form/Select",
+    "dijit/form/SimpleTextarea"
+], function (declare, lang, dom, domAttr, domClass, topic,
+    _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, registry,
+    WsPackageMaps, template) {
+    return declare("PackageMapValidateWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "PackageMapValidateWidget",
+        validateForm: null,
+        targetSelect: null,
+        packageContent: null,
+        validateResult: null,
+        validateButton: null,
+        targets: null,
+        processes: new Array(),
+        initalized: false,
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this.validateForm = registry.byId(this.id + "ValidatePM");
+            this.targetSelect = registry.byId(this.id + "TargetSelect");
+            this.processSelect = registry.byId(this.id + "ProcessSelect");
+            this.packageContent = registry.byId(this.id + "Package");
+            this.validateButton = registry.byId(this.id + "Validate");
+            this.validateResult = registry.byId(this.id + "ValidateResult");
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        init: function (params) {
+            if (this.initalized)
+                return;
+            this.initalized = true;
+            this.validateResult.set('style', 'visibility:hidden');
+            if (params.targets == undefined)
+                return;
+            this.initSelection(params.targets);
+        },
+
+        initSelections: function (targets) {
+            this.targets = targets;
+            if (this.targets.length > 0) {
+                for (var i = 0; i < this.targets.length; ++i)
+                    this.targetSelect.options.push({label: this.targets[i].Name, value: this.targets[i].Name});
+                this.targetSelect.set("value", this.targets[0].Name);
+                if (this.targets[0].Processes != undefined)
+                    this.updateProcessSelections(this.targets[0].Name);
+            }
+        },
+
+        addProcessSelections: function (processes) {
+            for (var i = 0; i < processes.length; ++i) {
+                var process = processes[i];
+                if ((this.processes != null) && (this.processes.indexOf(process) != -1))
+                    continue;
+                this.processes.push(process);
+                this.processSelect.options.push({label: process, value: process});
+            }
+        },
+
+        updateProcessSelections: function (targetName) {
+            this.processSelect.removeOption(this.processSelect.getOptions());
+            this.processSelect.options.push({label: 'ANY', value: '' });
+            for (var i = 0; i < this.targets.length; ++i) {
+                var target = this.targets[i];
+                if ((target.Processes != undefined) && ((targetName == '') || (targetName == target.Name)))
+                    this.addProcessSelections(target.Processes.Item);
+            }
+            this.processSelect.set("value", '');
+        },
+
+        addArrayToText: function (arrayTitle, arrayItems, text) {
+            if ((arrayItems.Item != undefined) && (arrayItems.Item.length > 0)) {
+                text += arrayTitle + ":\n";
+                for (i=0;i<arrayItems.Item.length;i++)
+                    text += "  " + arrayItems.Item[i] + "\n";
+                text += "\n";
+            }
+            return text;
+        },
+
+        validateResponseToText: function (response) {
+            var text = "";
+            if (!lang.exists("Errors", response) || (response.Errors.length < 1))
+                text += "No errors found\n";
+            else
+                text = this.addArrayToText("Error(s)", response.Errors, text);
+            if (!lang.exists("Warnings", response) || (response.Warnings.length < 1))
+                text += "No warnings found\n";
+            else
+                text = this.addArrayToText("Warning(s)", response.Warnings, text);
+
+            text += "\n";
+            text = this.addArrayToText("Queries without matching package", response.queries.Unmatched, text);
+            text = this.addArrayToText("Packages without matching queries", response.packages.Unmatched, text);
+            text = this.addArrayToText("Files without matching package definitions", response.files.Unmatched, text);
+            return text;
+        },
+
+        _onChangeTarget: function (event) {
+            this.processes.length = 0;
+            this.targetSelected  = this.targetSelect.getValue();
+            this.updateProcessSelections(this.targetSelected);
+        },
+
+        _onValidate: function (event) {
+            var request = { target: this.targetSelect.getValue() };
+            var type = this.validateForm.attr('value').ValidateType;
+            if (type == 'ActivePM') {
+                request['active'] = true;
+                request['process'] = this.processSelect.getValue();
+            } else {
+                var content = this.packageContent.getValue();
+                if (content == '') {
+                    alert('Package content not set');
+                    return;
+                }
+                request['content'] = content;
+            }
+            var context = this;
+            this.validateResult.setValue("");
+            this.validateButton.set("disabled", true);
+            WsPackageMaps.validatePackage(request, {
+                load: function (response) {
+                    var responseText = context.validateResponseToText(response);
+                    if (responseText == '')
+                        context.validateResult.setValue("(Empty)");
+                    else {
+                        responseText = '=====Validate Result=====\n\n' + responseText;
+                        context.validateResult.setValue(responseText);
+                    }
+                    context.validateResult.set('style', 'visibility:visible');
+                    context.validateButton.set("disabled", false);
+                },
+                error: function (errMsg, errStack) {
+                    context.showErrors(errMsg, errStack);
+                    context.validateButton.set("disabled", false);
+                }
+            });
+        },
+
+        showErrorMessage: function (message) {
+            dojo.publish("hpcc/brToaster", {
+                message: message,
+                type: "error",
+                duration: -1
+            });
+        },
+
+        showErrors: function (errMsg, errStack) {
+            var message = "Unknown Error";
+            if (errMsg != '')
+                message = "<h3>" + errMsg + "</h3>";
+            if (errStack != '')
+                message += "<p>" + errStack + "</p>";
+            this.showErrorMessage(message);
+        }
+    });
+});

+ 156 - 0
esp/files/scripts/PackageSourceWidget.js

@@ -0,0 +1,156 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2013 HPCC Systems.
+#
+#    Licensed 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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/dom",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/layout/BorderContainer",
+    "dijit/layout/ContentPane",
+    "dijit/registry",
+
+    "hpcc/WsPackageMaps",
+
+    "dojo/text!../templates/PackageSourceWidget.html",
+],
+    function (declare, lang, dom,
+            _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin,
+            BorderContainer, ContentPane, registry,
+            WsPackageMaps, template) {
+        return declare("PackageSourceWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+            templateString: template,
+            baseClass: "PackageSourceWidget",
+            borderContainer: null,
+
+            editor: null,
+            readOnly: true,
+
+            buildRendering: function (args) {
+                this.inherited(arguments);
+            },
+
+            postCreate: function (args) {
+                this.inherited(arguments);
+                this.borderContainer = registry.byId(this.id + "BorderContainer");
+            },
+
+            startup: function (args) {
+                this.inherited(arguments);
+            },
+
+            resize: function (args) {
+                this.inherited(arguments);
+                this.borderContainer.resize();
+            },
+
+            layout: function (args) {
+                this.inherited(arguments);
+            },
+
+            init: function (params) {
+                if (this.initalized)
+                    return;
+
+                this.initalized = true;
+                this.editor = CodeMirror.fromTextArea(document.getElementById(this.id + "XMLCode"), {
+                    tabMode: "indent",
+                    matchBrackets: true,
+                    gutter: true,
+                    lineNumbers: true,
+                    mode: this.isXmlContent ? "xml" : "ecl",
+                    readOnly: this.readOnly,
+                    gutter: this.isXmlContent ? true : false,
+                    onGutterClick: CodeMirror.newFoldFunction(CodeMirror.tagRangeFinder)
+                });
+                dom.byId(this.id + "XMLContent").style.backgroundColor = this.readOnly ? 0xd0d0d0 : 0xffffff;
+
+                var context = this;
+                if (this.isXmlContent) {
+                    WsPackageMaps.getPackageMapById(params, {
+                        load: function (response) {
+                            context.editor.setValue(response);
+                        },
+                        error: function (errMsg, errStack) {
+                            context.showErrors(errMsg, errStack);
+                        }
+                    });
+                }
+                else {
+                    WsPackageMaps.validatePackage(params, {
+                        load: function (response) {
+                            var responseText = context.validateResponseToText(response);
+                            //console.log(responseText);
+                            if (responseText == '')
+                                context.editor.setValue("(Empty)");
+                            else
+                                context.editor.setValue(responseText);
+                        },
+                        error: function (errMsg, errStack) {
+                            context.showErrors(errMsg, errStack);
+                        }
+                    });
+                }
+            },
+
+            showErrorMessage: function (message) {
+                dojo.publish("hpcc/brToaster", {
+                    message: message,
+                    type: "error",
+                    duration: -1
+                });
+            },
+
+            showErrors: function (errMsg, errStack) {
+                var message = "Unknown Error";
+                if (errMsg != '')
+                    message = "<h3>" + errMsg + "</h3>";
+                if (errStack != '')
+                    message += "<p>" + errStack + "</p>";
+                this.showErrorMessage(message);
+            },
+
+            addArrayToText: function (arrayTitle, arrayItems, text) {
+                if ((arrayItems.Item != undefined) && (arrayItems.Item.length > 0)) {
+                    text += arrayTitle + ":\n";
+                    for (i=0;i<arrayItems.Item.length;i++)
+                        text += "  " + arrayItems.Item[i] + "\n";
+                    text += "\n";
+                }
+                return text;
+            },
+
+            validateResponseToText: function (response) {
+                var text = "";
+                if (!lang.exists("Errors", response) || (response.Errors.length < 1))
+                    text += "No errors found\n";
+                else
+                    text = this.addArrayToText("Error(s)", response.Errors, text);
+                if (!lang.exists("Warnings", response) || (response.Warnings.length < 1))
+                    text += "No warnings found\n";
+                else
+                    text = this.addArrayToText("Warning(s)", response.Warnings, text);
+
+                text += "\n";
+                text = this.addArrayToText("Queries without matching package", response.queries.Unmatched, text);
+                text = this.addArrayToText("Packages without matching queries", response.packages.Unmatched, text);
+                text = this.addArrayToText("Files without matching package definitions", response.files.Unmatched, text);
+                return text;
+            }
+        });
+    });

+ 229 - 0
esp/files/scripts/WsPackageMaps.js

@@ -0,0 +1,229 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2013 HPCC Systems.
+#
+#    Licensed 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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/_base/Deferred",
+    "dojo/_base/array",
+    "dojo/store/util/QueryResults",
+
+    "hpcc/ESPRequest"
+], function (declare, lang, Deferred, arrayUtil, QueryResults,
+    ESPRequest) {
+    return {
+        PackageMapQuery: function (params) {
+            return ESPRequest.send("WsPackageProcess", "ListPackages", params);
+        },
+
+        errorMessageCallback: function (callback, message, errorStack)        {
+            if (callback && callback.error) {
+                callback.error(message, errorStack);
+            }
+        },
+
+        checkExceptions: function (callback, response) {
+            if (!lang.exists("Exceptions.Exception", response))
+                return true;
+            var exceptionCode = response.Exceptions.Exception[0].Code;
+            var exceptionMSG = response.Exceptions.Exception[0].Message;
+            this.errorMessageCallback(callback, "Exception:", "Code:"+exceptionCode);
+            return false;
+        },
+
+        checkStatus: function (callback, hasStatus, status) {
+            if (hasStatus && (status.Code == 0))
+                return true;
+            if (callback && callback.error) {
+                if (!hasStatus)
+                    this.errorMessageCallback(callback, "(Invalid response)", "");
+                else
+                    this.errorMessageCallback(callback, status.Description, "");
+            }
+            return false;
+        },
+
+        getPackageMapById: function (params, callback) {
+            var request = {
+                PackageMapId: params.packageMap
+            };
+
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "GetPackageMapById", {
+                request: request,
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("GetPackageMapByIdResponse.status", response),
+                        response.GetPackageMapByIdResponse.status))
+                    {
+                        if (!lang.exists("GetPackageMapByIdResponse.Info", response))
+                            callback.load("(No content)");
+                        else
+                            callback.load(response.GetPackageMapByIdResponse.Info);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+
+        GetPackageMapSelectOptions: function (params, callback) {
+            var request = {
+                IncludeTargets: params.includeTargets,
+                IncludeProcesses: params.includeProcesses,
+                IncludeProcessFilters: params.includeProcessFilters
+            };
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "GetPackageMapSelectOptions", {
+                request: {},
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("GetPackageMapSelectOptionsResponse.status", response),
+                        response.GetPackageMapSelectOptionsResponse.status))
+                    {
+                        callback.load(response.GetPackageMapSelectOptionsResponse);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+
+        listProcessFilters: function (callback) {
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "ListProcessFilters", {
+                request: {},
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("ListProcessFiltersResponse.status", response),
+                        response.ListProcessFiltersResponse.status))
+                    {
+                        if (!lang.exists("ListProcessFiltersResponse.ProcessFilters", response))
+                            callback.load("(No content)");
+                        else
+                            callback.load(response.ListProcessFiltersResponse.ProcessFilters);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+
+        validatePackage: function ( params, callback) {
+            var request = { Target: params.target };
+            if ( params.packageMap )
+                request['PMID'] = params.packageMap;
+            if ( params.process )
+                request['Process'] = params.process;
+            if ( params.content )
+                request['Info'] = params.content;
+            if ( params.active )
+                request['Active'] = params.active;
+
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "ValidatePackage", {
+                request: request,
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("ValidatePackageResponse.status", response),
+                        response.ValidatePackageResponse.status))
+                    {
+                        //console.log(response.ValidatePackageResponse);
+                        callback.load(response.ValidatePackageResponse);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+
+        activatePackageMap: function (packageMaps, callback) {
+            var request = {
+                Target: packageMaps[0].Target,
+                Process: packageMaps[0].Process,
+                PackageMap: packageMaps[0].Id
+            };
+
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "ActivatePackage", {
+                request: request,
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("ActivatePackageResponse.status", response),
+                        response.ActivatePackageResponse.status))
+                    {
+                        callback.load(response.ActivatePackageResponse);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+        deactivatePackageMap: function (packageMaps, callback) {
+            var request = {
+                Target: packageMaps[0].Target,
+                Process: packageMaps[0].Process,
+                PackageMap: packageMaps[0].Id
+            };
+
+            var context = this;
+            return ESPRequest.send("WsPackageProcess", "DeActivatePackage", {
+                request: request,
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("DeActivatePackageResponse.status", response),
+                        response.DeActivatePackageResponse.status))
+                    {
+                        callback.load(response.DeActivatePackageResponse);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        },
+        deletePackageMap: function (packageMaps, callback) {
+            var context = this;
+            var request = {};
+            arrayUtil.forEach(packageMaps, function (item, idx) {
+                request["PackageMaps.PackageMapEntry." + idx + ".Id"] = item.Id;
+                request["PackageMaps.PackageMapEntry." + idx + ".Target"] = item.Target;
+                request["PackageMaps.PackageMapEntry." + idx + ".Process"] = item.Process;
+            });
+            lang.mixin(request, {
+                "PackageMaps.PackageMapEntry.itemcount": packageMaps.length
+            });
+            return ESPRequest.send("WsPackageProcess", "DeletePackage", {
+                request: request,
+                load: function (response) {
+                    if (context.checkExceptions(callback, response) &&
+                        context.checkStatus(callback, lang.exists("DeletePackageResponse.status", response),
+                        response.DeletePackageResponse.status))
+                    {
+                        callback.load(response.DeletePackageResponse);
+                    }
+                },
+                error: function (err) {
+                    context.errorMessageCallback(callback, err.message, err.stack);
+                }
+            });
+        }
+    };
+});

+ 39 - 0
esp/files/templates/PackageMapDetailsWidget.html

@@ -0,0 +1,39 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%;" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}TabContainer" data-dojo-props="region: 'center', tabPosition: 'top'" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.TabContainer">
+            <div id="${id}Summary" style="width: 100%; height: 100%" data-dojo-props='title:"Summary", iconClass:"iconWorkunit"' data-dojo-type="dijit.layout.BorderContainer">
+                <div id="${id}Toolbar" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.Toolbar">
+                    <div id="${id}Activate" data-dojo-attach-event="onClick:_onActivate" data-dojo-type="dijit.form.Button">Activate</div>
+                    <div id="${id}Deactivate" data-dojo-attach-event="onClick:_onDeactivate" data-dojo-type="dijit.form.Button">Deactivate</div>
+                    <div id="${id}Delete" data-dojo-attach-event="onClick:_onDelete" data-dojo-type="dijit.form.Button">Delete</div>
+                    <span data-dojo-type="dijit.ToolbarSeparator"></span>
+                </div>
+                <div data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
+                    <h2>
+                        <div id="${id}StateIdImage" title="" class="iconWorkunit" ></div>&nbsp<span id="${id}PMID" class="bold">PMID</span>
+                    </h2>
+                    <form id="${id}SummaryForm">
+                        <ul>
+                            <li>
+                                <label class="Prompt" for="Target">Target:</label>
+                                <div><input id="${id}Target" data-dojo-props="trim:true" data-dojo-type="dijit.form.TextBox" disabled/></div>
+                            </li>
+                            <li>
+                                <label class="Prompt" for="Process">Process Filter:</label>
+                                <div><input id="${id}Process" data-dojo-props="trim:true" data-dojo-type="dijit.form.TextBox" disabled/></div>
+                            </li>
+                            <!--li>
+                                <label class="Prompt" for="Description">Description:</label>
+                                <div><input id="${id}Description" data-dojo-props="trim:true" data-dojo-type="dijit.form.TextBox"/></div>
+                            </li-->
+                        </ul>
+                    </form>
+                </div>
+            </div>
+            <div id="${id}XML" title="XML" data-dojo-props="isXmlContent: true" data-dojo-type="PackageSourceWidget">
+            </div>
+            <div id="${id}Validate" title="Validate" data-dojo-type="PackageSourceWidget">
+            </div>
+        </div>
+    </div>
+</div>

+ 83 - 0
esp/files/templates/PackageMapQueryWidget.html

@@ -0,0 +1,83 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%;" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}TabContainer" data-dojo-props="region: 'center', tabPosition: 'top'" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.TabContainer">
+            <div id="${id}Packages" style="width: 100%; height: 100%" data-dojo-props='title:"Package Maps"' data-dojo-type="dijit.layout.BorderContainer">
+                <div id="${id}Toolbar" class="topPanel" style="height: 4em;" data-dojo-props="region: 'top'" data-dojo-type="dijit.Toolbar">
+                    <b>Target:</b>
+                    <div id="${id}TargetSelect" name="TargetSelect" style="width: 6em;" data-dojo-attach-event="onChange:_onChangeTarget" data-dojo-type="dijit.form.Select">
+                    </div>
+                    <span data-dojo-type="dijit.ToolbarSeparator"></span>
+                    <b>Process:</b>
+                    <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-type="dijit.form.Select">
+                    </div>
+                    <span data-dojo-type="dijit.ToolbarSeparator"></span>
+                    <!--b>Process Filter:</b>
+                    <div id="${id}ProcessFilterSelect" name="ProcessFilterSelect" style="width: 10em;" data-dojo-type="dijit.form.Select">
+                    </div>
+                    <span data-dojo-type="dijit.ToolbarSeparator"></span-->
+                    <div id="${id}Refresh" data-dojo-attach-event="onClick:_onRefresh" data-dojo-props="iconClass:'iconRefresh'" data-dojo-type="dijit.form.Button">Reload</div>
+                    <br>
+                    <div id="${id}Open" data-dojo-attach-event="onClick:_onOpen" data-dojo-type="dijit.form.Button">Open</div>
+                    <div id="${id}AddNew" data-dojo-attach-event="onClick:_onAdd" data-dojo-type="dijit.form.Button">Add</div>
+                    <div id="${id}Activate" data-dojo-attach-event="onClick:_onActivate" data-dojo-type="dijit.form.Button">Activate</div>
+                    <div id="${id}Deactivate" data-dojo-attach-event="onClick:_onDeactivate" data-dojo-type="dijit.form.Button">Deactivate</div>
+                    <div id="${id}Delete" data-dojo-attach-event="onClick:_onDelete" data-dojo-type="dijit.form.Button">Delete</div>
+                </div>
+                <div id="${id}PackagesGrid" data-dojo-props="region: 'center', plugins: { pagination: { pageSizes: [25, 50, 100, 'All'], defaultPageSize: 50, description: true, sizeSwitch: true, pageStepper: true, gotoButton: true, maxPageStep: 4, position: 'bottom' }
+                    , indirectSelection: {
+                        headerSelector: true,
+                        width: '20px',
+                        styles: 'text-align: center;'
+                    }
+                }" data-dojo-type="dojox.grid.EnhancedGrid">
+                </div>
+                <div id="${id}AddProcessMapDialog" data-dojo-type="dijit/Dialog" data-dojo-id="${id}AddProcessMapDialog" title="Add Process Map">
+                    <div data-dojo-type="dijit/form/Form" id="${id}AddProcessMapForm" data-dojo-id="${id}AddProcessMapForm" method="post" enctype="multipart/form-data" >
+                       <table id="${id}AddProcessMapTable" class="dijitDialogPaneContentArea">
+                            <tr>
+                                <td><input id="${id}AddProcessMapFileUploader" name="${id}AddProcessMapFileUploader" multiple="false" type="file" data-dojo-type="dojox.form.Uploader" data-dojo-attach-event="onChange:_onCheckAddProcessMapInput" label="Select Package File"/></td>
+                            </tr>
+                            <tr>
+                                <td><div id="${id}AddProcessMapFileToUpload" data-dojo-type="dojox/form/uploader/FileList" data-dojo-props='uploaderId:"${id}AddProcessMapFileUploader"'>
+                                </div></td>
+                            </tr>
+                            <tr>
+                                <td><label for="${id}AddProcessMapId" style="width: 15em;">Id:</label></td>
+                                <td><input name="${id}AddProcessMapId" id="${id}AddProcessMapId" data-dojo-attach-event="onKeyUp:_onAddProcessMapIdKeyUp" data-dojo-props="trim: true" data-dojo-type="dijit/form/TextBox"</td>
+                            </tr>
+                            <tr>
+                                <td><label for="${id}AddProcessMapTargetSelect">Target:</label></td>
+                                <td><div id="${id}AddProcessMapTargetSelect" name="${id}AddProcessMapTargetSelect" data-dojo-attach-event="onChange:_onChangeAddProcessMapTarget" data-dojo-type="dijit.form.Select">
+                                </div></td>
+                            </tr>
+                            <tr>
+                                <td><label for="${id}AddProcessMapProcessSelect">Process&nbsp;Filter:</label></td>
+                                <td>
+                                    <div id="${id}AddProcessMapProcessSelect" name="${id}AddProcessMapProcessSelect" data-dojo-type="dijit.form.Select">
+                                    </div>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td><label for="${id}AddProcessMapDaliIP">Remote&nbsp;Dali&nbsp;IP&nbsp;Address:</label></td>
+                                <td><input name="${id}AddProcessMapDaliIP" id="${id}AddProcessMapDaliIP" data-dojo-props="trim: true" data-dojo-type="dijit/form/TextBox"></td>
+                            </tr>
+                            <tr>
+                                <td><label for="${id}AddProcessMapActivate">Activate:</label></td>
+                                <td><input id="${id}AddProcessMapActivate" name="${id}AddProcessMapActivate" data-dojo-type="dijit/form/CheckBox" value="" checked/></td>
+                            </tr>
+                            <tr>
+                                <td><label for="${id}AddProcessMapOverWrite">OverWrite:</label></td>
+                                <td><input id="${id}AddProcessMapOverWrite" name="${id}AddProcessMapOverWrite" data-dojo-type="dijit/form/CheckBox" value=""/></td>
+                            </tr>
+                            <tr>
+                                <td/>
+                                <td><input id="${id}AddProcessMapDialogSubmit" type="submit" label="Submit" data-dojo-type="dijit.form.Button"/>
+                                <input id="${id}AddProcessMapDialogClose" type="button" label="Close" data-dojo-type="dijit.form.Button"/></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 45 - 0
esp/files/templates/PackageMapValidateWidget.html

@@ -0,0 +1,45 @@
+<div class="${baseClass}">
+    <div id="${id}ValidatePM" data-dojo-id="${id}ValidatePM" data-dojo-type="dijit/form/Form" encType="multipart/form-data" action="" method="">
+        <table id="${id}ValidatePMTable">
+            <tr>
+                <td width="140px"><b>Target:</b></td>
+                <td>
+                    <div id="${id}TargetSelect" name="TargetSelect" style="width: 8em;" data-dojo-attach-event="onChange:_onChangeTarget" data-dojo-type="dijit/form/Select">
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <td width="140px"><b>Process:</b></td>
+                <td>
+                    <div id="${id}ProcessSelect" name="ProcessSelect" style="width: 8em;" data-dojo-type="dijit/form/Select">
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <td><b>Validate:</b></td>
+                <td>
+                    <input name="ValidateType" id="ActivePM"  value="ActivePM" type="radio" data-dojo-type="dijit/form/RadioButton" checked/>Active Package Map
+                    <input name="ValidateType" id="PMContent"  value="PMContent"type="radio" data-dojo-type="dijit/form/RadioButton"/>Package Content
+                </td>
+            </tr>
+            <tr>
+                <td valign='top'><b>Package Content:</b></td>
+                <td>
+                    <textarea id="${id}Package" name="${id}Package" data-dojo-type="dijit/form/SimpleTextarea" rows="15" cols="80" style="width:100%;height:80%;"></textarea>
+                </td>
+            </tr>
+            <tr>
+                <td/>
+                <td>
+                    <div id="${id}Validate" data-dojo-attach-event="onClick:_onValidate" data-dojo-type="dijit.form.Button">Validate</div>
+                </td>
+            </tr>
+            <tr>
+                <td/>
+                <td>
+                    <textarea id="${id}ValidateResult" data-dojo-type="dijit/form/SimpleTextarea" rows="15" cols="100"></textarea>
+                </td>
+            </tr>
+        </table>
+    </div>
+</div>

+ 7 - 0
esp/files/templates/PackageSourceWidget.html

@@ -0,0 +1,7 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%; overflow: hidden" data-dojo-props="splitter: false, gutters:false" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}XMLContent" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
+            <textarea id="${id}XMLCode">...Loading...</textarea>
+        </div>
+    </div>
+</div>

+ 59 - 1
esp/scm/ws_packageprocess.ecm

@@ -18,6 +18,12 @@ ESPstruct BasePackageStatus
     string Description;
 };
 
+ESPstruct PackageMapEntry
+{
+    string Id;
+    string Target;
+    string Process;
+};
 
 ESPrequest AddPackageRequest
 {
@@ -45,6 +51,7 @@ ESPrequest DeletePackageRequest
     string PackageMap;
     string Process;
     bool GlobalScope(0);
+    ESParray<ESPstruct PackageMapEntry, PackageMap> PackageMaps;
 };
 
 ESPresponse [exceptions_inline] DeletePackageResponse
@@ -90,6 +97,17 @@ ESPresponse [exceptions_inline] GetPackageResponse
     string Info;
 };
 
+ESPrequest GetPackageMapByIdRequest
+{
+    string PackageMapId;
+};
+
+ESPresponse [exceptions_inline] GetPackageMapByIdResponse
+{
+    ESPstruct BasePackageStatus status;
+    string Info;
+};
+
 ESPrequest ListPackageRequest
 {
     string Target;
@@ -106,8 +124,10 @@ ESPstruct PackageListMapData
 {
     string Id;
     string Target;
+    [min_ver("1.01")] string Process;
     ESParray<ESPstruct PackageListData> PkgListData;
     boolean Active;
+    [min_ver("1.01")] string Description;
 };
 
 ESPresponse [exceptions_inline] ListPackageResponse
@@ -116,10 +136,24 @@ ESPresponse [exceptions_inline] ListPackageResponse
     ESParray<ESPstruct PackageListMapData> PkgListMapData;
 };
 
+ESPrequest [nil_remove] ListPackagesRequest
+{
+    string Target;
+    string Process;
+    string ProcessFilter;
+};
+
+ESPresponse [nil_remove, exceptions_inline] ListPackagesResponse
+{
+    ESPstruct BasePackageStatus status;
+    ESParray<ESPstruct PackageListMapData> PackageMapList;
+};
+
 ESPrequest ValidatePackageRequest
 {
     string Info;
     string Target;
+    string Process;
     bool Active;
     string PMID;
     string QueryIdToVerify;
@@ -175,7 +209,28 @@ ESPresponse [exceptions_inline] GetQueryFileMappingResponse
 };
 
 
-ESPservice [version("1.00"), default_client_version("1.00"), exceptions_inline("./smc_xslt/exceptions.xslt")] WsPackageProcess
+ESPstruct TargetData
+{
+    string Name;
+    string Type;
+    ESParray<string> Processes;
+};
+
+ESPrequest GetPackageMapSelectOptionsRequest
+{
+    bool IncludeTargets(true);
+    bool IncludeProcesses(true);
+    bool IncludeProcessFilters(true);
+};
+
+ESPresponse [exceptions_inline] GetPackageMapSelectOptionsResponse
+{
+    ESPstruct BasePackageStatus status;
+    ESParray<ESPstruct TargetData> Targets;
+    ESParray<string> ProcessFilters;
+};
+
+ESPservice [version("1.01"), default_client_version("1.01"), exceptions_inline("./smc_xslt/exceptions.xslt")] WsPackageProcess
 {
     ESPmethod Echo(EchoRequest, EchoResponse);
     ESPmethod AddPackage(AddPackageRequest, AddPackageResponse);
@@ -183,9 +238,12 @@ ESPservice [version("1.00"), default_client_version("1.00"), exceptions_inline("
     ESPmethod ActivatePackage(ActivatePackageRequest, ActivatePackageResponse);
     ESPmethod DeActivatePackage(DeActivatePackageRequest, DeActivatePackageResponse);
     ESPmethod ListPackage(ListPackageRequest, ListPackageResponse);
+    ESPmethod ListPackages(ListPackagesRequest, ListPackagesResponse);
     ESPmethod GetPackage(GetPackageRequest, GetPackageResponse);
+    ESPmethod GetPackageMapById(GetPackageMapByIdRequest, GetPackageMapByIdResponse);
     ESPmethod ValidatePackage(ValidatePackageRequest, ValidatePackageResponse);
     ESPmethod GetQueryFileMapping(GetQueryFileMappingRequest, GetQueryFileMappingResponse);
+    ESPmethod GetPackageMapSelectOptions(GetPackageMapSelectOptionsRequest, GetPackageMapSelectOptionsResponse);
 };
 
 SCMexportdef(WsPackageProcess);

+ 2 - 2
esp/services/ws_fs/ws_fsBinding.cpp

@@ -464,7 +464,7 @@ void CFileSpraySoapBindingEx::downloadFile(IEspContext &context, CHttpRequest* r
     return;
 }
 
-int CFileSpraySoapBindingEx::onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response,   const char *service, const char *method, StringArray& fileNames, IMultiException *me)
+int CFileSpraySoapBindingEx::onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response,   const char *service, const char *method, StringArray& fileNames, StringArray& files, IMultiException *me)
 {
     if (!me || (me->ordinality()==0))
     {
@@ -514,7 +514,7 @@ int CFileSpraySoapBindingEx::onFinishUpload(IEspContext &ctx, CHttpRequest* requ
         if ((ctx.getResponseFormat() == ESPSerializationXML) || (ctx.getResponseFormat() == ESPSerializationJSON))
             response->handleExceptions(NULL, me, "FileSpray", "UploadFile", NULL);
         else
-            return EspHttpBinding::onFinishUpload(ctx, request, response, service, method, fileNames, me);
+            return EspHttpBinding::onFinishUpload(ctx, request, response, service, method, fileNames, files, me);
     }
 
     return 0;

+ 1 - 1
esp/services/ws_fs/ws_fsBinding.hpp

@@ -69,7 +69,7 @@ public:
     }
 
     int onGetInstantQuery(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *service, const char *method);
-    int onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response, const char *service, const char *method, StringArray& fileNames, IMultiException *me);
+    int onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response, const char *service, const char *method, StringArray& fileNames, StringArray& files, IMultiException *me);
 
 private:
     IPropertyTree* createPTreeForXslt(const char* method, const char* dfuwuid);

+ 1 - 0
esp/services/ws_packageprocess/packageprocess_errors.h

@@ -34,5 +34,6 @@
 #define PKG_LOAD_PACKAGEMAP_FAILED   PKG_PROCESS_ERROR_START+11
 #define PKG_INVALID_CLUSTER_TYPE   PKG_PROCESS_ERROR_START+12
 #define PKG_INVALID_QUERY_NAME   PKG_PROCESS_ERROR_START+13
+#define PKG_INFO_NOT_DEFINED   PKG_PROCESS_ERROR_START+14
 
 #endif

+ 371 - 22
esp/services/ws_packageprocess/ws_packageprocessService.cpp

@@ -296,16 +296,13 @@ void getAllPackageListInfo(IPropertyTree *mapTree, StringBuffer &info)
     }
     info.append("</PackageMap>");
 }
-void listPkgInfo(const char *target, const char *process, IArrayOf<IConstPackageListMapData>* results)
+
+void listPkgInfo(double version, const char *target, const char *process, IPropertyTree* pkgSetRegistry, IArrayOf<IConstPackageListMapData>* results)
 {
     Owned<IRemoteConnection> globalLock = querySDS().connect("/PackageMaps/", myProcessSession(), RTM_LOCK_WRITE|RTM_CREATE_QUERY, SDS_LOCK_TIMEOUT);
     if (!globalLock)
         throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali /PackageMaps");
     IPropertyTree *root = globalLock->queryRoot();
-    Owned<IPropertyTree> pkgSetRegistry = getPkgSetRegistry(process, true);
-    if (!pkgSetRegistry)
-        throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali for process %s", (process && *process) ? process : "*");
-
     StringBuffer xpath("PackageMap");
     if (target && *target)
         xpath.appendf("[@querySet='%s']", target);
@@ -319,13 +316,31 @@ void listPkgInfo(const char *target, const char *process, IArrayOf<IConstPackage
             StringBuffer xpath;
             xpath.append("PackageMap[@id='").append(id).append("']");
             IPropertyTree *mapTree = root->queryPropTree(xpath);
+            if (!mapTree)
+                continue;
             Owned<IEspPackageListMapData> res = createPackageListMapData("", "");
             res->setActive(item.getPropBool("@active"));
+            if (process && *process && (version >= 1.01))
+                res->setProcess(process);
             getPackageListInfo(mapTree, res);
+            if (target && *target)
+                res->setTarget(target);
+            else
+                res->setTarget(item.queryProp("@querySet"));
             results->append(*res.getClear());
         }
     }
 }
+
+void listPkgInfo(double version, const char *target, const char *process, IArrayOf<IConstPackageListMapData>* results)
+{
+    Owned<IPropertyTree> pkgSetRegistry = getPkgSetRegistry((process && *process) ? process : "*", true);
+    if (!pkgSetRegistry)
+        throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali for process %s", (process && *process) ? process : "*");
+
+    listPkgInfo(version, target, process, pkgSetRegistry, results);
+}
+
 void getPkgInfo(const char *target, const char *process, StringBuffer &info)
 {
     Owned<IRemoteConnection> globalLock = querySDS().connect("/PackageMaps/", myProcessSession(), RTM_LOCK_WRITE|RTM_CREATE_QUERY, SDS_LOCK_TIMEOUT);
@@ -456,6 +471,41 @@ void activatePackageMapInfo(const char *target, const char *name, const char *pr
     }
 }
 
+bool CWsPackageProcessEx::readPackageMapString(const char *packageMapString, StringBuffer &target, StringBuffer &process, StringBuffer &packageMap)
+{
+    if (!packageMapString || !*packageMapString)
+        return false;
+
+    StringArray plist;
+    plist.appendListUniq(packageMapString, ",");
+    if (plist.length() < 3)
+        return false;
+
+    target.set(plist.item(0));
+    process.set(plist.item(1));
+    packageMap.set(plist.item(2));
+    if (!target.length() || !packageMap.length())
+        return false;
+    return true;
+}
+
+void CWsPackageProcessEx::getPkgInfoById(const char *packageMapId, IPropertyTree* tree)
+{
+    if (!packageMapId || !*packageMapId)
+        return;
+
+    Owned<IRemoteConnection> globalLock = querySDS().connect("/PackageMaps/", myProcessSession(), RTM_LOCK_WRITE|RTM_CREATE_QUERY, SDS_LOCK_TIMEOUT);
+    if (!globalLock)
+        throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali /PackageMaps");
+
+    StringBuffer xpath;
+    xpath.append("PackageMap[@id='").append(packageMapId).append("']");
+    IPropertyTree *root = globalLock->queryRoot();
+    IPropertyTree *mapTree = root->queryPropTree(xpath);
+    if (mapTree)
+        mergePTree(tree, mapTree);
+}
+
 bool CWsPackageProcessEx::onAddPackage(IEspContext &context, IEspAddPackageRequest &req, IEspAddPackageResponse &resp)
 {
     resp.updateStatus().setCode(0);
@@ -501,20 +551,57 @@ bool CWsPackageProcessEx::onAddPackage(IEspContext &context, IEspAddPackageReque
     return true;
 }
 
+void CWsPackageProcessEx::deletePackage(const char *packageMap, const char *target, const char *process, bool globalScope, StringBuffer &returnMsg, int &returnCode)
+{
+    bool ret = deletePkgInfo(packageMap, target, process, globalScope);
+    (ret) ? returnMsg.append("Successfully ") : returnMsg.append("Unsuccessfully ");
+    returnMsg.append("deleted ").append(packageMap).append(" from ").append(target).append(";");
+    if (!ret)
+        returnCode = -1;
+    return;
+}
+
 bool CWsPackageProcessEx::onDeletePackage(IEspContext &context, IEspDeletePackageRequest &req, IEspDeletePackageResponse &resp)
 {
-    resp.updateStatus().setCode(0);
-    StringAttr pkgMap(req.getPackageMap());
-    StringAttr processName(req.getProcess());
-    if (processName.length()==0)
-        processName.set("*");
+    int returnCode = 0;
+    StringBuffer returnMsg;
+    IArrayOf<IConstPackageMapEntry>& packageMaps = req.getPackageMaps();
+    ForEachItemIn(p, packageMaps)
+    {
+        IConstPackageMapEntry& item=packageMaps.item(p);
+        if (!item.getId() || !*item.getId())
+        {
+            returnMsg.appendf("PackageMap[%d]: Package map Id not specified; ", p);
+            continue;
+        }
+        if (!item.getTarget() || !*item.getTarget())
+        {
+            returnMsg.appendf("PackageMap[%d]: Target not specified;", p);
+            continue;
+        }
 
-    bool ret = deletePkgInfo(pkgMap.get(), req.getTarget(), processName.get(), req.getGlobalScope());
-    StringBuffer msg;
-    (ret) ? msg.append("Successfully ") : msg.append("Unsuccessfully ");
-    msg.append("deleted ").append(pkgMap.get()).append(" from ").append(req.getTarget());
+        StringBuffer target, processName, packageMap;
+        packageMap.set(item.getId());
+        target.set(item.getTarget());
+        if (!item.getProcess() || !*item.getProcess())
+            processName.set("*");
+        else
+            processName.set(item.getProcess());
 
-    resp.updateStatus().setDescription(msg.str());
+        deletePackage(packageMap.str(), target.str(), processName.str(), req.getGlobalScope(), returnMsg, returnCode);
+    }
+
+    if (!packageMaps.length())
+    {
+        StringAttr pkgMap(req.getPackageMap());
+        StringAttr processName(req.getProcess());
+        if (!processName.length())
+            processName.set("*");
+        deletePackage(pkgMap.get(), req.getTarget(), processName.get(), req.getGlobalScope(), returnMsg, returnCode);
+    }
+
+    resp.updateStatus().setDescription(returnMsg.str());
+    resp.updateStatus().setCode(returnCode);
     return true;
 }
 
@@ -537,11 +624,76 @@ bool CWsPackageProcessEx::onListPackage(IEspContext &context, IEspListPackageReq
     resp.updateStatus().setCode(0);
     IArrayOf<IConstPackageListMapData> results;
     StringAttr process(req.getProcess());
-    listPkgInfo(req.getTarget(), process.length() ? process.get() : "*", &results);
+    listPkgInfo(context.getClientVersion(), req.getTarget(), process.length() ? process.get() : "*", &results);
     resp.setPkgListMapData(results);
     return true;
 }
 
+bool CWsPackageProcessEx::onListPackages(IEspContext &context, IEspListPackagesRequest &req, IEspListPackagesResponse &resp)
+{
+    double version = context.getClientVersion();
+    const char* targetReq = req.getTarget();
+    const char* processReq = req.getProcess();
+    const char* processFilterReq = req.getProcessFilter();
+    IArrayOf<IConstPackageListMapData> results;
+    if ((!processReq || !*processReq) && (processFilterReq && *processFilterReq))
+        listPkgInfo(version, targetReq, processFilterReq, &results);
+    else
+    {
+        Owned<IRemoteConnection> conn = querySDS().connect("/PackageSets", myProcessSession(), RTM_LOCK_READ, SDS_LOCK_TIMEOUT);
+        if (!conn)
+            throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali for process");
+        Owned<IPropertyTree> pkgSetRegistryRoot = conn->getRoot();
+        if (!pkgSetRegistryRoot)
+            throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve package information from dali for process");
+        Owned<IPropertyTreeIterator> iter = pkgSetRegistryRoot->getElements("PackageSet");
+        ForEach(*iter)
+        {
+            try
+            {
+                Owned<IPropertyTree> pkgSetRegistry= &iter->get();
+                StringBuffer process;
+                pkgSetRegistry->getProp("@process", process);
+                if (process.length() && (streq(process.str(), "*") || WildMatch(process.str(), processReq, true)))
+                    listPkgInfo(version, targetReq, process.str(), pkgSetRegistry, &results);
+            }
+            catch(IException* e)
+            {
+                int err = e->errorCode();
+                //Dali throws an exception if packagemap is not available for a process.
+                if (err == PKG_DALI_LOOKUP_ERROR)
+                    e->Release();
+                else
+                    throw e;
+            }
+        }
+        if ((version >=1.01) && processReq && *processReq)
+        {//Show warning if multiple packages are active for the same target.
+            ForEachItemIn(i, results)
+            {
+                IEspPackageListMapData& r1 = (IEspPackageListMapData&) results.item(i);
+                if (!r1.getActive())
+                    continue;
+                const char* target1 = r1.getTarget();
+                for (unsigned ii = i+1; ii<results.length(); ++ii)
+                {
+                    IEspPackageListMapData& r2 = (IEspPackageListMapData&) results.item(ii);
+                    if (!r2.getActive())
+                        continue;
+                    if (!streq(target1, r2.getTarget()))
+                        continue;
+                    StringBuffer warning;
+                    warning.appendf("Error: package %s is also active.", r1.getId());
+                    r2.setDescription(warning.str());
+                }
+            }
+        }
+    }
+    resp.setPackageMapList(results);
+    resp.updateStatus().setCode(0);
+    return true;
+}
+
 bool CWsPackageProcessEx::onGetPackage(IEspContext &context, IEspGetPackageRequest &req, IEspGetPackageResponse &resp)
 {
     resp.updateStatus().setCode(0);
@@ -552,6 +704,31 @@ bool CWsPackageProcessEx::onGetPackage(IEspContext &context, IEspGetPackageReque
     return true;
 }
 
+bool CWsPackageProcessEx::onGetPackageMapById(IEspContext &context, IEspGetPackageMapByIdRequest &req, IEspGetPackageMapByIdResponse &resp)
+{
+    try
+    {
+        const char* pkgMapId = req.getPackageMapId();
+        if (!pkgMapId && !*pkgMapId)
+            throw MakeStringException(PKG_MISSING_PARAM, "PackageMap Id not specified");
+
+        StringBuffer info;
+        Owned<IPropertyTree> tree = createPTree("PackageMaps");
+        getPkgInfoById(pkgMapId, tree);
+        toXML(tree, info);
+        resp.setInfo(info.str());
+        resp.updateStatus().setCode(0);
+    }
+    catch (IException *e)
+    {
+        StringBuffer retMsg;
+        resp.updateStatus().setDescription(e->errorMessage(retMsg).str());
+        resp.updateStatus().setCode(-1);
+    }
+
+    return true;
+}
+
 bool CWsPackageProcessEx::onValidatePackage(IEspContext &context, IEspValidatePackageRequest &req, IEspValidatePackageResponse &resp)
 {
     StringArray warnings;
@@ -574,25 +751,31 @@ bool CWsPackageProcessEx::onValidatePackage(IEspContext &context, IEspValidatePa
     if (!process.length())
         throw MakeStringException(PKG_TARGET_NOT_DEFINED, "Roxie process not found");
 
-    const char *pmid = req.getPMID();
+    const char* pmID = req.getPMID();
+    const char* info = req.getInfo();
+
     if (req.getActive()) //validate active map
     {
         mapTree.setown(resolveActivePackageMap(process.str(), target, true));
         if (!mapTree)
             throw MakeStringException(PKG_PACKAGEMAP_NOT_FOUND, "Active package map not found");
     }
-    else if (pmid && *pmid)
+    else if (pmID && *pmID)
     {
-        mapTree.setown(getPackageMapById(req.getGlobalScope() ? NULL : target, pmid, true));
+        mapTree.setown(getPackageMapById(req.getGlobalScope() ? NULL : target, pmID, true));
         if (!mapTree)
-            throw MakeStringException(PKG_PACKAGEMAP_NOT_FOUND, "Package map %s not found", req.getPMID());
+            throw MakeStringException(PKG_PACKAGEMAP_NOT_FOUND, "Package map %s not found", pmID);
     }
-    else
+    else if (info && *info)
     {
-        mapTree.setown(createPTreeFromXMLString(req.getInfo()));
+        mapTree.setown(createPTreeFromXMLString(info));
         if (!mapTree)
             throw MakeStringException(PKG_LOAD_PACKAGEMAP_FAILED, "Error processing package file content");
     }
+    else
+    {
+        throw MakeStringException(PKG_PACKAGEMAP_NOT_FOUND, "package map not specified");
+    }
 
     if (req.getCheckDFS())
     {
@@ -623,6 +806,101 @@ bool CWsPackageProcessEx::onValidatePackage(IEspContext &context, IEspValidatePa
     resp.updateQueries().setUnmatched(unmatchedQueries);
     resp.updatePackages().setUnmatched(unusedPackages);
     resp.updateFiles().setUnmatched(unmatchedFiles);
+    resp.updateStatus().setCode(0);
+    return true;
+}
+
+bool CWsPackageProcessEx::onGetPackageMapSelectOptions(IEspContext &context, IEspGetPackageMapSelectOptionsRequest &req, IEspGetPackageMapSelectOptionsResponse &resp)
+{
+    try
+    {
+        bool includeTargets = req.getIncludeTargets();
+        bool includeProcesses = req.getIncludeProcesses();
+        if (includeTargets || includeProcesses)
+        {
+            Owned<IRemoteConnection> connEnv = querySDS().connect("Environment", myProcessSession(), RTM_LOCK_READ, SDS_LOCK_TIMEOUT);
+            if (!connEnv)
+                throw MakeStringException(PKG_DALI_LOOKUP_ERROR,"Failed to get environment information.");
+
+            IArrayOf<IConstTargetData> targets;
+            CConstWUClusterInfoArray clusters;
+            getEnvironmentClusterInfo(connEnv->queryRoot(), clusters);
+            ForEachItemIn(c, clusters)
+            {
+                SCMStringBuffer str;
+                IConstWUClusterInfo &cluster = clusters.item(c);
+                Owned<IEspTargetData> target = createTargetData("", "");
+                target->setName(cluster.getName(str).str());
+                ClusterType clusterType = cluster.getPlatform();
+                if (clusterType == ThorLCRCluster)
+                    target->setType(THORCLUSTER);
+                else if (clusterType == RoxieCluster)
+                    target->setType(ROXIECLUSTER);
+                else
+                    target->setType(HTHORCLUSTER);
+                if (!includeProcesses)
+                {
+                    targets.append(*target.getClear());
+                    continue;
+                }
+                StringArray processes;
+                if (clusterType == ThorLCRCluster)
+                {
+                    const StringArray &thors = cluster.getThorProcesses();
+                    ForEachItemIn(i, thors)
+                    {
+                        const char* process = thors.item(i);
+                        if (process && *process)
+                            processes.append(process);
+                    }
+                }
+                else if (clusterType == RoxieCluster)
+                {
+                    SCMStringBuffer process;
+                    cluster.getRoxieProcess(process);
+                    if (process.length())
+                        processes.append(process.str());
+                }
+                else if (clusterType == HThorCluster)
+                {
+                    SCMStringBuffer process;
+                    cluster.getAgentQueue(process);
+                    if (process.length())
+                        processes.append(process.str());
+                }
+                if (processes.length())
+                    target->setProcesses(processes);
+                targets.append(*target.getClear());
+            }
+            resp.setTargets(targets);
+        }
+        if (req.getIncludeProcessFilters())
+        {
+            StringArray processFilters;
+            processFilters.append("*");
+            Owned<IRemoteConnection> pkgSet = querySDS().connect("/PackageSets/", myProcessSession(), RTM_LOCK_WRITE, SDS_LOCK_TIMEOUT);
+            if (pkgSet)
+            {
+                Owned<IPropertyTreeIterator> iter = pkgSet->queryRoot()->getElements("PackageSet");
+                ForEach(*iter)
+                {
+                    StringBuffer process;
+                    iter->query().getProp("@process", process);
+                    if (process.length() && !processFilters.contains(process.str()))
+                        processFilters.append(process.str());
+                }
+            }
+            resp.setProcessFilters(processFilters);
+        }
+        resp.updateStatus().setCode(0);
+    }
+    catch (IException *e)
+    {
+        StringBuffer retMsg;
+        resp.updateStatus().setDescription(e->errorMessage(retMsg).str());
+        resp.updateStatus().setCode(-1);
+    }
+
     return true;
 }
 
@@ -691,3 +969,74 @@ bool CWsPackageProcessEx::onGetQueryFileMapping(IEspContext &context, IEspGetQue
     resp.setSuperFiles(superArray);
     return true;
 }
+
+int CWsPackageProcessSoapBindingEx::onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response,
+    const char *service, const char *method, StringArray& fileNames, StringArray& files, IMultiException *meIn)
+{
+    if (meIn && (meIn->ordinality() > 0))
+    {
+        StringBuffer msg;
+        WARNLOG("Exception(s) in EspHttpBinding::onFinishUpload - %s", meIn->errorMessage(msg).append('\n').str());
+        if ((ctx.getResponseFormat() == ESPSerializationXML) || (ctx.getResponseFormat() == ESPSerializationJSON))
+        {
+            response->handleExceptions(NULL, meIn, "FileSpray", "UploadFile", NULL);
+            return 0;
+        }
+        else
+            return EspHttpBinding::onFinishUpload(ctx, request, response, service, method, fileNames, files, meIn);
+    }
+
+    StringBuffer respStr;
+    Owned<IEspWsPackageProcess> iserv = (IEspWsPackageProcess*)getService();
+    if(iserv == NULL)
+    {
+        WARNLOG("Exception(s) in %s::%s - Service not available", service, method);
+	    respStr.append("{\"Code\":-1,\"Exception\":\"Service not available\"}");
+    }
+    else
+    {
+        checkRequest(ctx);
+        Owned<CAddPackageRequest> esp_request = new CAddPackageRequest(&ctx, "WsPackageProcess", request->queryParameters(), request->queryAttachments());
+        Owned<CAddPackageResponse> esp_response = new CAddPackageResponse("WsPackageProcess");
+        StringBuffer source;
+        source.setf("WsPackageProcess::%s()", method);
+        Owned<IMultiException> me = MakeMultiException(source.str());
+        try
+        {
+            if (!files.length())
+                throw MakeStringExceptionDirect(PKG_INFO_NOT_DEFINED, "Package content not found");
+
+            esp_request->setInfo(files.item(0));
+            iserv->onAddPackage(ctx, *esp_request.get(), *esp_response.get());
+        }
+        catch (IMultiException* mex)
+        {
+	        me->append(*mex);
+	        mex->Release();
+        }
+        catch (IException* e)
+        {
+	        me->append(*e);
+        }
+        catch (...)
+        {
+	        me->append(*MakeStringExceptionDirect(-1, "Unknown Exception"));
+        }
+
+        if (!me->ordinality())
+        {
+            respStr.append("{\"Code\":0,\"Description\":\"Package Map added\"}");
+        }
+        else
+        {
+            StringBuffer msg;
+            WARNLOG("Exception(s) in %s::%s - %s", service, method, me->errorMessage(msg).str());
+            respStr.appendf("{\"Code\":-1,\"Exception\":\"%s\"}", msg.str());
+        }
+    }
+
+    response->setContent(respStr.str());
+    response->setContentType(HTTP_TYPE_APPLICATION_JSON_UTF8);
+    response->send();
+    return 0;
+}

+ 13 - 0
esp/services/ws_packageprocess/ws_packageprocessService.hpp

@@ -20,6 +20,9 @@
 
 #include "ws_packageprocess_esp.ipp"
 
+#define THORCLUSTER "thor"
+#define HTHORCLUSTER "hthor"
+#define ROXIECLUSTER "roxie"
 
 
 class CWsPackageProcessSoapBindingEx : public CWsPackageProcessSoapBinding
@@ -32,12 +35,19 @@ public:
     virtual void getNavigationData(IEspContext &context, IPropertyTree & data)
     {
         //Add navigation link here
+        IPropertyTree *folderQueryset = ensureNavFolder(data, "Queries", NULL);
+        CEspBinding::ensureNavLink(*folderQueryset, "Package Maps", "/esp/files/stub.htm?Widget=PackageMapQueryWidget", "Browse Package Maps", NULL, NULL, 2);
     }
+    int onFinishUpload(IEspContext &ctx, CHttpRequest* request, CHttpResponse* response, const char *service,
+        const char *method, StringArray& fileNames, StringArray& files, IMultiException *me);
 };
 
 
 class CWsPackageProcessEx : public CWsPackageProcess
 {
+    bool readPackageMapString(const char *packageMapString, StringBuffer &target, StringBuffer &process, StringBuffer &packageMap);
+    void getPkgInfoById(const char *packageMapId, IPropertyTree* tree);
+    void deletePackage(const char *packageMap, const char *target, const char *process, bool globalScope, StringBuffer &returnMsg, int &returnCode);
 public:
     IMPLEMENT_IINTERFACE;
     virtual ~CWsPackageProcessEx(){};
@@ -53,6 +63,9 @@ public:
     virtual bool onGetPackage(IEspContext &context, IEspGetPackageRequest &req, IEspGetPackageResponse &resp);
     virtual bool onValidatePackage(IEspContext &context, IEspValidatePackageRequest &req, IEspValidatePackageResponse &resp);
     virtual bool onGetQueryFileMapping(IEspContext &context, IEspGetQueryFileMappingRequest &req, IEspGetQueryFileMappingResponse &resp);
+    virtual bool onListPackages(IEspContext &context, IEspListPackagesRequest &req, IEspListPackagesResponse &resp);
+    virtual bool onGetPackageMapSelectOptions(IEspContext &context, IEspGetPackageMapSelectOptionsRequest &req, IEspGetPackageMapSelectOptionsResponse &resp);
+    virtual bool onGetPackageMapById(IEspContext &context, IEspGetPackageMapByIdRequest &req, IEspGetPackageMapByIdResponse &resp);
 };
 
 #endif //_ESPWIZ_ws_packageprocess_HPP__