瀏覽代碼

Merge pull request #2009 from afishbeck/new_ecldirect

Refactor EclDirect in EclWatch with security and extended features

Reviewed-by: Gavin Halliday <ghalliday@hpccsystems.com>
Gavin Halliday 13 年之前
父節點
當前提交
c378d1bd9f

+ 16 - 0
common/workunit/workunit.cpp

@@ -7724,6 +7724,22 @@ extern WORKUNIT_API void secAbortWorkUnit(const char *wuid, ISecManager &secmgr,
         abortWorkUnit(wuid);
 }
 
+extern WORKUNIT_API void submitWorkUnit(const char *wuid, ISecManager *secmgr, ISecUser *secuser)
+{
+    if (secmgr && secuser)
+        return secSubmitWorkUnit(wuid, *secmgr, *secuser);
+    if (secuser)
+        return submitWorkUnit(wuid, secuser->getName(), secuser->credentials().getPassword());
+    submitWorkUnit(wuid, "", "");
+}
+
+extern WORKUNIT_API void abortWorkUnit(const char *wuid, ISecManager *secmgr, ISecUser *secuser)
+{
+    if (secmgr && secuser)
+        return secAbortWorkUnit(wuid, *secmgr, *secuser);
+    abortWorkUnit(wuid);
+}
+
 bool CLocalWorkUnit::hasWorkflow() const
 {
     return p->hasProp("Workflow");

+ 2 - 0
common/workunit/workunit.hpp

@@ -1163,6 +1163,8 @@ extern WORKUNIT_API StringBuffer &exportWorkUnitToXML(const IConstWorkUnit *wu,
 extern WORKUNIT_API void exportWorkUnitToXMLFile(const IConstWorkUnit *wu, const char * filename, unsigned extraXmlFlags);
 extern WORKUNIT_API void submitWorkUnit(const char *wuid, const char *username, const char *password);
 extern WORKUNIT_API void abortWorkUnit(const char *wuid);
+extern WORKUNIT_API void submitWorkUnit(const char *wuid, ISecManager *secmgr, ISecUser *secuser);
+extern WORKUNIT_API void abortWorkUnit(const char *wuid, ISecManager *secmgr, ISecUser *secuser);
 extern WORKUNIT_API void secSubmitWorkUnit(const char *wuid, ISecManager &secmgr, ISecUser &secuser);
 extern WORKUNIT_API void secAbortWorkUnit(const char *wuid, ISecManager &secmgr, ISecUser &secuser);
 extern WORKUNIT_API IWUResult * updateWorkUnitResult(IWorkUnit * w, const char *name, unsigned sequence);

+ 1 - 0
esp/eclwatch/ws_XSLT/CMakeLists.txt

@@ -100,6 +100,7 @@ FOREACH ( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/roxiequerydetails.xslt
     ${CMAKE_CURRENT_SOURCE_DIR}/roxiequerygraph.xslt
     ${CMAKE_CURRENT_SOURCE_DIR}/roxiequerygvcgraph.xslt
+    ${CMAKE_CURRENT_SOURCE_DIR}/run_ecl.xslt
     ${CMAKE_CURRENT_SOURCE_DIR}/scheduledwus.xslt
     ${CMAKE_CURRENT_SOURCE_DIR}/services.xslt
     ${CMAKE_CURRENT_SOURCE_DIR}/clusterprocesses.xslt

+ 115 - 0
esp/eclwatch/ws_XSLT/run_ecl.xslt

@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (C) 2012 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html"/>
+    <xsl:template match="RunEclEx">
+        <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+            <head>
+                <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+                <title>Run Ecl form</title>
+            </head>
+            <body>
+                <xsl:call-template name="form"/>
+            </body>
+        </html>
+    </xsl:template>
+    <xsl:template name="form">
+        <h4>Submit ECL text for execution:</h4>
+        <form method="POST">
+            <xsl:attribute name="action">
+                <xsl:choose>
+                    <xsl:when test="Redirect='Yes'">/EclDirect/RunEclEx?redirect</xsl:when>
+                    <xsl:otherwise>/EclDirect/RunEclEx/DisplayResult</xsl:otherwise>
+                </xsl:choose>
+            </xsl:attribute>
+            <table cellSpacing="0" cellPadding="0" width="90%" border="0">
+                <tbody>
+                    <br/>
+                    <br/>
+                    <tr>
+                        <td valign="top">
+                            <b>ECL Text: </b>
+                        </td>
+                        <td>
+                            <textarea name="eclText" rows="20" cols="80" >
+                            </textarea>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            <b>Cluster: </b>
+                        </td>
+                        <td>
+                            <select name="cluster">
+                                <xsl:for-each select="Cluster">
+                                    <option>
+                                        <xsl:value-of select="."/>
+                                    </option>
+                                </xsl:for-each>
+                            </select>
+                        </td>
+                    </tr>
+                    <xsl:if test="UseEclRepository='Yes'">
+                        <tr>
+                            <td>
+                                <b>Repository Label </b>
+                            </td>
+                            <td valign="top" rowspan="2">
+                                <input type="text" name="snapshot"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <b>(Legacy): </b>
+                            </td>
+                        </tr>
+                    </xsl:if>
+                    <xsl:if test="IncludeResults='Yes'">
+                        <tr>
+                            <input type="hidden" value="1" name="includeResults"/>
+                            <td>
+                                <b>Output: </b>
+                            </td>
+                            <td>
+                                <select name="format" >
+                                    <option value="Table" selected="1">Table</option>
+                                    <option value="Xml" >XML</option>
+                                    <option value="ExtendedXml" >Extended XML</option>
+                                </select>
+                            </td>
+                        </tr>
+                     </xsl:if>
+                     <tr>
+                        <td/>
+                        <td>
+                            <input type="checkbox" name="resultLimit" checked="1" value="100"/> Limit Result Count to 100.
+                        </td>
+                    </tr>
+                    <tr>
+                        <td/>
+                        <td>
+                            <input type="submit" value="Submit" name="S1"/>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        </form>
+    </xsl:template>
+    <xsl:template match="*|@*|text()"/>
+</xsl:stylesheet>

+ 41 - 1
esp/scm/ecldirect.ecm

@@ -16,10 +16,20 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ############################################################################## */
 
+ESPStruct [nil_remove] ECLDirectException
+{
+    string Source;
+    string Severity;
+    int Code;
+    string Message;
+    string FileName;
+    int LineNo;
+    int Column;
+};
+
 ESPrequest RunEclRequest
 {
     string userName;
-    string password;
     string cluster;
     boolean limitResults;
     string eclText;
@@ -32,10 +42,40 @@ ESPresponse [http_encode(0)] RunEclResponse
     EspResultSet results;
 };
 
+ESPenum RunEclExFormat : string
+{
+    None("None"),
+    Table("Table"),
+    Xml("Xml"),
+    ExtendedXml("ExtendedXml")
+};
+
+ESPrequest [nil_remove] RunEclExRequest
+{
+    [rows(28), cols(80)] string eclText;
+    string cluster;
+    string snapshot;
+    boolean includeResults;
+    boolean includeGraphs;
+    ESPenum RunEclExFormat format;
+    int wait(-1);
+    int resultLimit(0);
+};
+
+
+ESPresponse [nil_remove] RunEclExResponse
+{
+    string wuid;
+    ESParray<ESPstruct ECLDirectException> Errors;
+    string results;
+    string graphsXGMML;
+};
+
 
 ESPservice EclDirect
 {
     ESPmethod RunEcl(RunEclRequest, RunEclResponse);
+    ESPmethod RunEclEx(RunEclExRequest, RunEclExResponse);
 };
 
 

+ 3 - 0
esp/services/ecldirect/CMakeLists.txt

@@ -45,6 +45,7 @@ include_directories (
          ./../../../system/include 
          ./../../../common/environment 
          ./../../../common/workunit 
+         ./../../../common/wuwebview
          ./../../clients 
          ./../../../common/fileview2 
          ./../../../dali/base 
@@ -61,6 +62,7 @@ target_link_libraries ( EclDirect
          jlib
          xmllib 
          esphttp 
+         LdapSecurity
          mp 
          hrpc 
          remote 
@@ -71,6 +73,7 @@ target_link_libraries ( EclDirect
          eclrtl 
          deftype 
          workunit 
+         wuwebview
          jhtree 
          hql 
          fileview2 

+ 0 - 69
esp/services/ecldirect/EclDirectBinding.hpp

@@ -1,69 +0,0 @@
-/*##############################################################################
-
-    Copyright (C) 2011 HPCC Systems.
-
-    All rights reserved. This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU Affero General Public License as
-    published by the Free Software Foundation, either version 3 of the
-    License, or (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Affero General Public License for more details.
-
-    You should have received a copy of the GNU Affero General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-############################################################################## */
-
-#ifndef _EclDirectBinding_HPP__
-#define _EclDirectBinding_HPP__
-
-#include "ecldirect_esp.ipp"
-#include "environment.hpp"
-
-
-typedef MapStringTo<int> DedupeList;
-
-
-class CEclDirectSoapBindingEx : public CEclDirectSoapBinding
-{
-private:
-    StringArray m_clusterNames;
-    StringArray m_eclQueueNames;
-
-public:
-    CEclDirectSoapBindingEx()
-    {
-        initFromEnv();
-    }
-    
-    CEclDirectSoapBindingEx(IPropertyTree* cfg, const char *bindname=NULL, const char *procname=NULL):CEclDirectSoapBinding(cfg, bindname, procname)
-    {
-        initFromEnv();
-    }
-
-    int onGetXForm(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *service, const char *method)
-    {
-        return onGetForm(context, request, response, service, method);
-    }
-
-    void initFromEnv();
-
-    int onGetForm(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method);
-
-    int getMethodHtmlForm(IEspContext &context, CHttpRequest* request, const char *serv, const char *method, StringBuffer &form, bool bIncludeFormTag);
-
-    virtual int getMethodDescription(IEspContext &context, const char *serv, const char *method, StringBuffer &page);
-    virtual int getMethodHelp(IEspContext &context, const char *serv, const char *method, StringBuffer &page);
-
-    //overide the default behavior when the basic service method url is accessed (http://host:port/EclDirect/RunEcl)
-    //      by default that is a query with no parameters, but we can just display the form
-    //      instead of the user having to add "?form" to the end
-    virtual int onGetService(IEspContext &context, CHttpRequest* request,   CHttpResponse* response, const char *serv, const char *method, const char *pathex);
-    int getWsdlMessages(IEspContext &context, StringBuffer &content, const char *service, const char *method);
-    int getWsdlBindings(IEspContext &context, StringBuffer &content, const char *service, const char *method);
-};
-
-#endif //_EclDirectBinding_HPP__
-

+ 9 - 32
esp/services/ecldirect/EclDirectPlugin.cpp

@@ -19,74 +19,51 @@
 #pragma warning (disable : 4786)
 
 #include "ecldirect_esp.ipp"
-
-//ESP Bindings
 #include "http/platform/httpprot.hpp"
-
-//ESP Service
 #include "EclDirectService.hpp"
-#include "EclDirectBinding.hpp"
-
 #include "espplugin.hpp"
 
 extern "C"
 {
 
-//when we aren't loading dynamically
-// Change the function names when we stick with dynamic loading.
 ESP_FACTORY IEspService * esp_service_factory(const char *name, const char* type, IPropertyTree *cfg, const char *process)
 {
    if (strcmp(type, "EclDirect")==0)
    {
-      CEclDirectEx* service = new CEclDirectEx;
+      CEclDirectEx* service = new CEclDirectEx();
         service->init(cfg, process, name);
       return service;
    }
-    else
-    {
-        throw MakeStringException(-1, "Unknown service type %s", type);
-    }
+
+   ERRLOG("Unknown service type %s", type);
    return NULL;
 }
- 
-   
 
 ESP_FACTORY IEspRpcBinding * esp_binding_factory(const char *name, const char* type, IPropertyTree *cfg, const char *process)
 {
    if (strcmp(type, "EclDirectSoapBinding")==0)
         return new CEclDirectSoapBindingEx(cfg, name, process);
-    else
-        throw MakeStringException(-1, "Unknown binding type %s", type);
 
+   ERRLOG("Unknown binding type %s", type);
    return NULL;
 }
 
-
-
 ESP_FACTORY IEspProtocol * esp_protocol_factory(const char *name, const char* type, IPropertyTree *cfg, const char *process)
 {
     if (strcmp(type, "http_protocol")==0)
-    {
         return new CHttpProtocol;
-    }
-    else if(strcmp(type, "secure_http_protocol") == 0)
+
+    if(strcmp(type, "secure_http_protocol") == 0)
     {
         IPropertyTree *sslSettings;
         sslSettings = cfg->getPropTree(StringBuffer("Software/EspProcess[@name=\"").append(process).append("\"]").append("/EspProtocol[@name=\"").append(name).append("\"]").str());
         if(sslSettings != NULL)
-        {
             return new CSecureHttpProtocol(sslSettings);
-        }
-        else
-        {
-            throw MakeStringException(-1, "can't find ssl settings in the config file");
-        }
-    }
-    else
-    {
-        throw MakeStringException(-1, "Unknown protocol %s", name);
+        ERRLOG("can't find ssl settings in the config file");
+        return NULL;
     }
 
+    ERRLOG("Unknown protocol %s", name);
     return NULL;
 }
 

+ 244 - 261
esp/services/ecldirect/EclDirectService.cpp

@@ -19,11 +19,54 @@
 #pragma warning (disable : 4786)
 
 #include "EclDirectService.hpp"
-#include "EclDirectBinding.hpp"
 
-#include "daclient.hpp"
 #include "workunit.hpp"
 #include "fileview.hpp"
+#include "wuwebview.hpp"
+
+#define ECLDIRECT_ACCESS "EclDirectAccess"
+
+struct EclDirectWUExceptions
+{
+    EclDirectWUExceptions(IConstWorkUnit& cw);
+    operator IArrayOf<IEspECLDirectException>&() { return errors; }
+
+private:
+    IArrayOf<IEspECLDirectException> errors;
+};
+
+EclDirectWUExceptions::EclDirectWUExceptions(IConstWorkUnit& cw)
+{
+    Owned<IConstWUExceptionIterator> it = &cw.getExceptions();
+    ForEach(*it)
+    {
+        SCMStringBuffer s;
+        Owned<IEspECLDirectException> e= createECLDirectException();
+        IConstWUException &item = it->query();
+        e->setCode(item.getExceptionCode());
+        e->setSource(item.getExceptionSource(s).str());
+        e->setMessage(item.getExceptionMessage(s).str());
+        e->setFileName(item.getExceptionFileName(s).str());
+        e->setLineNo(item.getExceptionLineNo());
+        e->setColumn(item.getExceptionColumn());
+
+        switch (it->query().getSeverity())
+        {
+            default:
+            case ExceptionSeverityError:
+                e->setSeverity("Error");
+                break;
+            case ExceptionSeverityWarning:
+                e->setSeverity("Warning");
+                break;
+            case ExceptionSeverityInformation:
+                e->setSeverity("Info");
+                break;
+        }
+
+        errors.append(*e.getLink());
+    }
+}
 
 void CEclDirectEx::init(IPropertyTree *cfg, const char *process, const char *service)
 {
@@ -37,20 +80,56 @@ void CEclDirectEx::init(IPropertyTree *cfg, const char *process, const char *ser
         throw MakeStringException(-1, "EclDirect: Configuration Error: unable to load configuration");
     }
 
-    srvcfg->getProp("ClusterName", m_clustername);
+    srvcfg->getProp("ClusterName", defaultCluster);
 
-    m_def_timeout = srvcfg->getPropInt("WuTimeout", 60000);
-    m_deleteworkunits = cfg->getPropBool("DeleteWorkUnits", false);
+    defaultWait = srvcfg->getPropInt("WuTimeout", 60000);
+    deleteWorkunits = cfg->getPropBool("DeleteWorkUnits", false);
+}
+
+CEclDirectSoapBindingEx::CEclDirectSoapBindingEx(IPropertyTree* cfg, const char *binding, const char *process):CEclDirectSoapBinding(cfg, binding, process)
+{
+    StringBuffer xpath;
+    xpath.appendf("Software/EspProcess[@name='%s']", process);
+    IPropertyTree *procTree = cfg->queryPropTree(xpath.str());
+    if (!procTree)
+        throw MakeStringException(-1, "EclDirect Configuration Error: unable to find process");
+
+    xpath.set("EspBinding[@name='").append(binding).append("']/@port");
+    int port = procTree->getPropInt(xpath.str());
+    if (port)
+    {
+        xpath.set("EspBinding[@type='ws_workunitsSoapBinding'][@port='").append(port).append("']");
+        redirect = procTree->hasProp(xpath.str());
+    }
+
+    SCMStringBuffer s;
+    Owned<IStringIterator> it = getTargetClusters(NULL, NULL);
+    ForEach(*it)
+        clusters.append(it->str(s).str());
+    supportRepository = false;
+}
+
+inline void deleteEclDirectWorkunit(IWorkUnitFactory *factory, const char *wuid)
+{
+    try
+    {
+        factory->deleteWorkUnit(wuid);
+    }
+    catch (IException *e)
+    {
+        DBGLOG(e, "EclDirect Failed to delete workunit");
+    }
+    catch (...)
+    {
+    }
 }
 
 bool CEclDirectEx::onRunEcl(IEspContext &context, IEspRunEclRequest & req, IEspRunEclResponse & resp)
 {
-    bool sec_access = (context.querySecManager()!=NULL);
-    Owned <IWorkUnitFactory> factory;
-    if (sec_access)
-        factory.setown(getSecWorkUnitFactory(*context.querySecManager(), *context.queryUser()));
-    else
-        factory.setown(getWorkUnitFactory());
+    if (!context.validateFeatureAccess(ECLDIRECT_ACCESS, SecAccess_Full, false))
+        throw MakeStringException(-1, "EclDirect access permission denied.");
+
+    Owned <IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
 
     Owned <IWorkUnit> workunit = factory->createWorkUnit(NULL, "ECL-Direct", "user");
     Owned<IWUQuery> query = workunit->updateQuery();
@@ -58,22 +137,13 @@ bool CEclDirectEx::onRunEcl(IEspContext &context, IEspRunEclRequest & req, IEspR
     query.clear();
 
     StringBuffer user;
-    context.getUserID(user);
-    
-    if (user.length()==0)
+    if (!context.getUserID(user).length())
         user.append(req.getUserName());
-
-    StringBuffer pw;
-    context.getPassword(pw);
-
-    if (pw.length()==0)
-        pw.append(req.getPassword());
-    
     workunit->setUser((user.length()) ? user.str() : "user");
 
     const char* clustername = req.getCluster();
-    if (!clustername || *clustername==0 || !stricmp(clustername, "default")) 
-        clustername = m_clustername.str();
+    if (!clustername || *clustername==0 || strieq(clustername, "default")) 
+        clustername = defaultCluster.str();
 
     workunit->setClusterName(clustername);
     if (req.getLimitResults())
@@ -91,29 +161,20 @@ bool CEclDirectEx::onRunEcl(IEspContext &context, IEspRunEclRequest & req, IEspR
     workunit->setState(WUStateSubmitted);
     workunit.clear();
 
-    if (sec_access)
-        secSubmitWorkUnit(wuid.str(), *context.querySecManager(), *context.queryUser());
-    else
-        submitWorkUnit(wuid.str(), user.str(), pw.str());
+    submitWorkUnit(wuid.str(), context.querySecManager(), context.queryUser());
 
-    if (waitForWorkUnitToComplete(wuid.str(), m_def_timeout))
+    if (waitForWorkUnitToComplete(wuid.str(), defaultWait))
     {
         Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid.str(), false);
-    
+
         SCMStringBuffer resultXML;
-        getFullWorkUnitResultsXML(user.str(), pw.str(), cw.get(), resultXML, false);
+        getFullWorkUnitResultsXML(context.queryUserId(), context.queryPassword(), cw.get(), resultXML, false);
         resp.setResults(resultXML.str());
 
         cw.clear();
 
-        try
-        {
-            if (m_deleteworkunits)
-                factory->deleteWorkUnit(wuid.str());
-        }
-        catch (...)
-        {
-        }
+        if (deleteWorkunits)
+            deleteEclDirectWorkunit(factory, wuid.str());
     }
     else
     {
@@ -125,268 +186,190 @@ bool CEclDirectEx::onRunEcl(IEspContext &context, IEspRunEclRequest & req, IEspR
         resp.setResults(result.str());
     }
 
-    DBGLOG("EclDirect Request served");
     return true;
 }
 
-void CEclDirectSoapBindingEx::initFromEnv()
+bool CEclDirectEx::onRunEclEx(IEspContext &context, IEspRunEclExRequest & req, IEspRunEclExResponse & resp)
 {
-    try
-    {
-        Owned<IEnvironmentFactory> envFactory = getEnvironmentFactory();
-        Owned<IConstEnvironment> constEnv = envFactory->openEnvironmentByFile();
-        Owned<IPropertyTree> root = &constEnv->getPTree();
-        if (!root)
-            throw MakeStringException(20045, "Failed to get environment information");
-
-        Owned<IPropertyTreeIterator> clusters= root->getElements("Software/Topology/Cluster");
-        ForEach(*clusters)
-        {
-            IPropertyTree &cluster = clusters->query();                 
-            const char* name = cluster.queryProp("@name");
-            if (!name||!*name || cluster.hasProp("RoxieCluster[1]"))
-                continue;
-            m_clusterNames.append(name);
-        }
-    }
-    catch (IException *e)
-    {
-        StringBuffer msg;
-        ERRLOG("Exception getting environment information (%d) -- %s", e->errorCode(), e->errorMessage(msg).str());
-    }
-    catch (...)
+    if (!context.validateFeatureAccess(ECLDIRECT_ACCESS, SecAccess_Full, false))
+        throw MakeStringException(-1, "EclDirect access permission denied.");
+
+    const char* eclText = req.getEclText();
+    if (!eclText || !*eclText)
     {
-        ERRLOG("Unknown Exception getting environment information");
+        resp.setResults("<Exception><Source>ESP</Source><Message>No Ecl Text provided</Message></Exception>");
+        return true;
     }
 
-}
+    StringBuffer user;
+    context.getUserID(user);
 
-int CEclDirectSoapBindingEx::onGetForm(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method)
-{
-    StringBuffer serviceQName;
-    StringBuffer methodQName;
+    Owned <IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
+    Owned <IWorkUnit> workunit = factory->createWorkUnit(NULL, "ECL-Direct", (user.length()) ? user.str() : "user");
+    workunit->setUser((user.length()) ? user.str() : "user");
+
+    Owned<IWUQuery> query = workunit->updateQuery();
+    query->setQueryText(eclText);
+    query.clear();
 
-    if (!qualifyServiceName(context, serv, method, serviceQName, &methodQName))
+    const char* cluster = req.getCluster();
+    if (!cluster || !*cluster || !stricmp(cluster, "default"))
+        cluster = defaultCluster.str();
+    workunit->setClusterName(cluster);
+
+    const char* snapshot = req.getSnapshot();
+    if (snapshot && *snapshot)
+        workunit->setSnapshot(snapshot);
+
+    if (req.getResultLimit())
+        workunit->setResultLimit(req.getResultLimit());
+
+    // Execute it
+    SCMStringBuffer wuid;
+    workunit->getWuid(wuid);
+    workunit->setAction(WUActionRun);
+    workunit->setState(WUStateSubmitted);
+    workunit.clear();
+
+    resp.setWuid(wuid.str());
+
+    submitWorkUnit(wuid.str(), context.querySecManager(), context.queryUser());
+
+    if (!waitForWorkUnitToComplete(wuid.str(), (req.getWait_isNull()) ? defaultWait : req.getWait()))
     {
-        return onGetNotFound(context, request,  response, serv);
+        StringBuffer result;
+        result.appendf("<Exception><Source>ESP</Source><Message>Timed out waiting for job to complete: %s</Message></Exception>", wuid.str());
+        resp.setResults(result.str());
+        return true;
     }
-    else
+
+    if (!deleteWorkunits && context.queryRequestParameters()->hasProp("redirect"))
     {
-        StringBuffer page;
-
-        page.append(
-            "<html>"
-                "<head>"
-                    "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
-                    "<title>ESP Service form</title>"
-                    "<script language=\"JavaScript\" src=\"files_/calendar_xs.js\"></script>"
-                    "<script language=\"JavaScript\" src=\"files_/hint.js\"></script>"
-                "</head>"
-                "<body>"
-                    "<p align=\"center\" />"
-                    "<table cellSpacing=\"0\" cellPadding=\"1\" width=\"90%\" bgColor=\"#4775FF\" border=\"0\">"
-                        "<tbody>"
-                            "<tr align=\"middle\" bgColor=\"#4775FF\">"
-                                "<td height=\"23\">"
-                                    "<p align=\"left\">"
-                                        "<font color=\"#efefef\">");
-                                page.appendf("<b>%s [Version %g]</b>", serviceQName.str(), context.getClientVersion());
-                                page.append("</font>"
-                                    "</p>"
-                                "</td>"
-                            "</tr>"
-                            "<tr bgColor=\"#CBE5FF\">"
-                                "<td height=\"3\">"
-                                    "<p align=\"left\">");
-                                            
-                                page.appendf("<b>&gt; %s</b>", methodQName.str());
-                                page.append("</p>"
-                                "</td>"
-                            "</tr>"
-                            "<TR bgColor=\"#666666\">"
-                                "<TABLE cellSpacing=\"0\" width=\"90%\" bgColor=\"#efefef\" border=\"0\">"
-                                    "<TBODY>"
-                                        "<TR>"
-                                            "<TD vAlign=\"center\" align=\"left\">"
-                                                "<p align=\"left\"><br/>");
-                                                getMethodDescription(context, serviceQName.str(), methodQName.str(), page);
-                                                page.append("</p>"
-                                            "</TD>"
-                                        "</TR>"
-                                    "</TBODY>"
-                                "</TABLE>"
-                            "</TR>"
-                            "<TR bgColor=\"#666666\">"
-                                "<TABLE cellSpacing=\"0\" width=\"90%\" bgColor=\"#efefef\" border=\"0\">"
-                                    "<TBODY>"
-                                        "<TR>"
-                                            "<TD vAlign=\"center\" align=\"left\">");
-                                                getMethodHtmlForm(context, request, serviceQName.str(), methodQName.str(), page, true);
-                                                page.append("</TD>"
-                                        "</TR>"
-                                    "</TBODY>"
-                                "</TABLE>"
-                            "</TR>"
-                        "</tbody>"
-                    "</table>"
-                    "<BR />"
-                "</body>"
-            "</html>");
-
-        response->setContent(page.str());
-        response->setContentType("text/html");
+        StringBuffer url("/WsWorkunits/WUInfo?Wuid=");
+        resp.setRedirectUrl(url.append(wuid).str());
+        return true;
     }
-    
-    response->send();
 
-    return 0;
-}
+    Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid.str(), false);
+    EclDirectWUExceptions errors(*cw);
+    resp.setErrors(errors);
 
-int CEclDirectSoapBindingEx::getMethodHtmlForm(IEspContext &context, CHttpRequest* request, const char *serv, const char *method, StringBuffer &form, bool bIncludeFormTag)
-{
-    if (Utils::strcasecmp(method, "runecl")==0)
+    if (req.getIncludeResults())
     {
-        if (bIncludeFormTag)
-            form.append("<form method=\"POST\" action=\"/EclDirect/RunEcl\">");
-
-        form.append("<p align=\"left\">");
-        form.append("<b>ECL Text: </b><br/><textarea name=\"eclText\" rows=\"20\" cols=\"80\" ></textarea>");
-        form.append("<br/><input type=\"button\" value=\"options &gt;&gt;\" onclick=\"if (option_span.style.display=='block'){option_span.style.display='none';value='options &gt;&gt;';} else {option_span.style.display='block'; value='&lt;&lt; options';}\"/>");
-        
-        form.append("<span id=\"option_span\" style=\"display:none\">");
-
-
-        form.append("<br/><b>Cluster: </b><br/>");
-        form.append("<select name=\"cluster\" >");
-        form.append("<option value=\"default\" selected=\"1\" /> default");
-        ForEachItemIn(indC, m_clusterNames)
+        StringBuffer results;
+        CRunEclExFormat outputFormat = req.getFormat();
+        Owned<IWuWebView> web = createWuWebView(wuid.str(), NULL, getCFD(), true);
+        if (!web)
+            results.appendf("<Exception><Source>ESP</Source><Message>Failed loading result workunit %s</Message></Exception>", wuid.str());
+        else if (outputFormat == CRunEclExFormat_Table)
         {
-            const char *cluster = m_clusterNames.item(indC);
-            form.appendf("<option value=\"%s\"/>%s", cluster, cluster);
+            StringBuffer xsltfile(getCFD());
+            web->applyResultsXSLT(xsltfile.append("xslt/wsecl3_result.xslt").str(), results);
         }
-        form.append("</select><br/>");
-        form.append("<br/><b>Repository Label (Legacy): </b><br/><input type=\"text\" name=\"snapshot\"/><br/>");
-            
-        form.append("<br/><input type=\"checkbox\" name=\"limitResults\" checked=\"1\" value=\"1\"/> Limit Result Count to 100.");
-        form.append("<br/><input type=\"checkbox\" name=\"rawxml_\" /> Output Xml?");
-        form.append("</span>");
-        
-        form.append("<br/><input type=\"submit\" value=\"Submit\" name=\"S1\" />");
-        form.append("</p>");
-
-        if (bIncludeFormTag)
-            form.append("</form>");
+        else
+        {
+            unsigned xmlflags = WWV_ADD_RESULTS_TAG | WWV_ADD_RESPONSE_TAG;
+            if (outputFormat != CRunEclExFormat_ExtendedXml)
+                xmlflags |= WWV_OMIT_SCHEMAS;
+            if (context.queryRequestParameters()->hasProp("display_xslt"))
+                xmlflags |= WWV_USE_DISPLAY_XSLT;
+            else
+                xmlflags |= WWV_OMIT_XML_DECLARATION;
+            web->expandResults(results, xmlflags);
+        }
+        resp.setResults(results.str());
     }
-    else
-        return CEclDirectSoapBinding::getMethodHtmlForm(context, request, serv, method, form, bIncludeFormTag);
 
-    return 0;
-}
-
-int CEclDirectSoapBindingEx::getMethodDescription(IEspContext &context, const char *serv, const char *method, StringBuffer &page)
-{
-    if (Utils::strcasecmp(method, "RunEcl")==0)
+    if (req.getIncludeGraphs())
     {
-        page.append("Submit ECL text for execution.");
+        Owned<IConstWUGraphIterator> it = &cw->getGraphs(GraphTypeAny);
+        StringBuffer xgmml("<Graphs>");
+        SCMStringBuffer s;
+        ForEach(*it)
+            xgmml.append(it->query().getXGMML(s, true).str());
+        xgmml.append("</Graphs>");
+        resp.setGraphsXGMML(xgmml.str());
     }
-    return 0;
+
+    if (deleteWorkunits)
+        deleteEclDirectWorkunit(factory, wuid.str());
+
+    return true;
 }
-int CEclDirectSoapBindingEx::getMethodHelp(IEspContext &context, const char *serv, const char *method, StringBuffer &page)
+
+static void xsltTransform(const char* xml, const char* sheet, IProperties *params, StringBuffer& ret)
 {
-    return 0;
+    if(!checkFileExists(sheet))
+        throw MakeStringException(-1, "Could not find stylesheet %s.",sheet);
+    Owned<IXslProcessor> proc = getXslProcessor();
+    Owned<IXslTransform> trans = proc->createXslTransform();
+    trans->setXmlSource(xml, strlen(xml));
+    trans->loadXslFromFile(sheet);
+    trans->copyParameters(params);
+    trans->transform(ret);
 }
 
-//overide the default behavior when the basic service method url is accessed (http://host:port/EclDirect/RunEcl)
-//      by default that is a query with no parameters, but we can just display the form
-//      instead of the user having to add "?form" to the end
-int CEclDirectSoapBindingEx::onGetService(IEspContext &context, CHttpRequest* request,   CHttpResponse* response, const char *serv, const char *method, const char *pathex)
+int CEclDirectSoapBindingEx::sendRunEclExForm(IEspContext &context, CHttpRequest* request, CHttpResponse* response)
 {
-    StringBuffer propStr;
-    IProperties *props = request->queryParameters();
-
-    //can't just get properties count?
-    if (props && props->hasProp("eclText"))
-        return onGetQuery(context, request, response, serv, method);
+    StringBuffer xml;
+    xml.append("<RunEclEx clientVersion='").append(context.getClientVersion()).append("'>");
+    appendXMLTag(xml, "UseEclRepository", (supportRepository) ? "Yes" : "No");
+    appendXMLTag(xml, "Redirect", (redirect) ? "Yes" : "No");
+    appendXMLTag(xml, "IncludeResults", (redirect) ? "No" : "Yes");
+    ForEachItemIn(i, clusters)
+        appendXMLTag(xml, "Cluster", clusters.item(i));
+    xml.append("</RunEclEx>");
+
+    StringBuffer xslt(getCFD());
+    xslt.append("./smc_xslt/run_ecl.xslt");
+
+    StringBuffer html;
+    xsltTransform(xml.str(), xslt.str(), NULL, html);
+    response->setContent(html.str());
+    response->setContentType(HTTP_TYPE_TEXT_HTML_UTF8);
+    response->send();
 
-    return onGetXForm(context, request, response,   serv, method);
+    return 0;
 }
 
-int CEclDirectSoapBindingEx::getWsdlMessages(IEspContext &context, StringBuffer &content, const char *service, const char *method)
+inline const char *runEclExFormatMimeType(CRunEclExFormat format)
 {
-    bool allMethods = (method==NULL || *method==0);
-    if (allMethods || Utils::strcasecmp(method, "RunEcl")==0)
-    {
-        content.append("<message name=\"RunEclSoapIn\">");
-        content.append("<part name=\"parameters\" element=\"tns:RunEclRequest\"/>");
-        content.append("</message>");
-
-        content.append("<message name=\"RunEclSoapOut\">");
-        content.append("<part name=\"parameters\" element=\"tns:RunEclResponse\"/>");
-        content.append("</message>");
-
-        content.append("<message name=\"RunEclHttpGetIn\">");
-        content.append("<part name=\"parameters\" element=\"tns:string\"/>");
-        content.append("</message>");
-
-        content.append("<message name=\"RunEclHttpGetOut\">");
-        content.append("<part name=\"parameters\" element=\"tns:string\"/>");
-        content.append("</message>");
-
-        content.append("<message name=\"RunEclHttpPostIn\">");
-        content.append("<part name=\"parameters\" element=\"tns:string\"/>");
-        content.append("</message>");
-
-        content.append("<message name=\"RunEclHttpPostOut\">");
-        content.append("<part name=\"parameters\" element=\"tns:string\"/>");
-        content.append("</message>");
-
-    }
-    return 0;
+    if (format == CRunEclExFormat_Table)
+        return "text/html; charset=utf-8";
+    return "text/xml; charset=utf-8";
 }
 
-int CEclDirectSoapBindingEx::getWsdlBindings(IEspContext &context, StringBuffer &content, const char *service, const char *method)
+int CEclDirectSoapBindingEx::onGet(CHttpRequest* request, CHttpResponse* response)
 {
-    bool allMethods = (method==NULL || *method==0);
-    content.append("<binding name=\"EclDirectServiceSoap\" type=\"tns:EclDirectServiceSoap\">");
-    content.append("<soap:binding transport=\"http://schemas.xmlsoap.org/soap/http\" style=\"document\"/>");
+    const char *path = request->queryPath();
+    if (strieq(path, "/EclDirect/RunEclEx/Form"))
+        return sendRunEclExForm(*request->queryContext(), request, response);
 
-    if (allMethods || Utils::strcasecmp(method, "RunEcl")==0)
+    if(strieq(path, "/EclDirect/RunEclEx/DisplayResult"))
     {
-        content.append("<operation name=\"RunEcl\">");
-        content.append("<soap:operation soapAction=\"urn:hpccsystems:ws:ecldirect:runecl\" style=\"document\"/>");
-        content.append("<input>");
-        content.append("<soap:body use=\"literal\"/>");
-        content.append("</input>");
-        content.append("<output><soap:body use=\"literal\"/></output>");
-        content.append("</operation>");
+        IEspContext& context = *request->queryContext();
+        request->queryParameters()->setProp("display_xslt", 1);
 
-    }
-    content.append("</binding>");
-    content.append("<binding name=\"EclDirectServiceHttpGet\" type=\"tns:EclDirectServiceHttpGet\">");
-    content.append("<http:binding verb=\"GET\"/>");
+        CRunEclExRequest reqObj(&context, "EclDirect", request->queryParameters(), request->queryAttachments());
+        CRunEclExResponse respObj("EclDirect");
+        theService->onRunEclEx(context, *QUERYINTERFACE(&reqObj, IEspRunEclExRequest), *QUERYINTERFACE(&respObj, IEspRunEclExResponse));
 
-    if (allMethods || Utils::strcasecmp(method, "RunEcl")==0)
-    {
-        content.append("<operation name=\"RunEcl\">");
-        content.append("<http:operation location=\"/RunEcl\"/>");
-        content.append("<input><http:urlEncoded/></input>");
-        content.append("<output><mime:mimeXml part=\"Body\"/></output>");
-        content.append("</operation>");
+        const char *result = respObj.getResults();
+        if (result && *result)
+        {
+            response->setContent(result);
+            response->setContentType(runEclExFormatMimeType(reqObj.getFormat()));
+        }
+        else
+        {
+            response->setContent("No result in Ecl execution.");
+            response->setContentType("text/html");
+        }
 
+        response->setStatus(HTTP_STATUS_OK);
+        response->send();
+        return 0;
     }
-    content.append("</binding>");
-    content.append("<binding name=\"EclDirectServiceHttpPost\" type=\"tns:EclDirectServiceHttpPost\">");
-    content.append("<http:binding verb=\"POST\"/>");
 
-    if (allMethods || Utils::strcasecmp(method, "RunEcl")==0)
-    {
-        content.append("<operation name=\"RunEcl\">");
-        content.append("<http:operation location=\"/RunEcl\"/>");
-        content.append("<input><mime:content type=\"application/x-www-form-urlencoded\"/></input>");
-        content.append("<output><mime:mimeXml part=\"Body\"/></output>");
-        content.append("</operation>");
-
-    }
-    content.append("</binding>");
-    return 0;
+    return CEclDirectSoapBinding::onGet(request,response);
 }

+ 35 - 8
esp/services/ecldirect/EclDirectService.hpp

@@ -16,28 +16,55 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ############################################################################## */
 
-#ifndef _ESPWIZ_EclDirect_HPP__
-#define _ESPWIZ_EclDirect_HPP__
+#ifndef _EclDirectService_HPP__
+#define _EclDirectService_HPP__
 
 #include "ecldirect_esp.ipp"
 
 class CEclDirectEx : public CEclDirect
 {
 private:
-    StringBuffer m_clustername;
-    StringBuffer m_eclserver;
-    int m_def_timeout;
-    bool m_deleteworkunits;
+    StringBuffer defaultCluster;
+    int defaultWait;
+    bool deleteWorkunits;
 
 public:
    IMPLEMENT_IINTERFACE;
 
-    CEclDirectEx() : m_def_timeout(0){}
+    CEclDirectEx() : defaultWait(0){}
 
     virtual void init(IPropertyTree *cfg, const char *process, const char *service);
 
     bool onRunEcl(IEspContext &context, IEspRunEclRequest &req, IEspRunEclResponse &resp);
+    bool onRunEclEx(IEspContext &context, IEspRunEclExRequest &req, IEspRunEclExResponse &resp);
 };
 
-#endif //_ESPWIZ_EclDirect_HPP__
+class CEclDirectSoapBindingEx : public CEclDirectSoapBinding
+{
+private:
+    CEclDirectEx *theService;
+
+    StringArray clusters;
+    bool supportRepository;
+    bool redirect;
+
+public:
+    CEclDirectSoapBindingEx(IPropertyTree* cfg, const char *bindname=NULL, const char *procname=NULL);
+
+    void addService(const char *name, const char *host, unsigned short port, IEspService &service)
+    {
+        theService = dynamic_cast<CEclDirectEx*>(&service);
+        CEspBinding::addService(name, host, port, service);
+    }
+
+    virtual void getNavigationData(IEspContext &context, IPropertyTree & data)
+    {
+        IPropertyTree *folder = ensureNavFolder(data, "ECL", "Run Ecl code and review Ecl workunits", NULL, false, 2);
+        ensureNavLink(*folder, "Run Ecl", "/EclDirect/RunEclEx/Form", "Submit ECL text for execution", NULL, NULL, 3);
+    }
+
+    virtual int onGet(CHttpRequest* request, CHttpResponse* response);
+    int sendRunEclExForm(IEspContext &context, CHttpRequest* request, CHttpResponse* response);
+};
 
+#endif //_EclDirectService_HPP__

+ 3 - 3
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -122,9 +122,9 @@ public:
     {
         if (!batchWatchFeaturesOnly)
         {
-            IPropertyTree *folder = ensureNavFolder(data, "ECL Workunits", NULL, NULL, false, 2);
-            ensureNavLink(*folder, "Search Workunits", "/WsWorkunits/WUQuery?form_", "Search for ECL workunits", NULL, NULL, 1);
-            ensureNavLink(*folder, "Browse Workunits", "/WsWorkunits/WUQuery", "Browse a list of ECL workunits", NULL, NULL, 2);
+            IPropertyTree *folder = ensureNavFolder(data, "ECL", "Run Ecl code and review Ecl workunits", NULL, false, 2);
+            ensureNavLink(*folder, "Search Workunits", "/WsWorkunits/WUQuery?form_", "Search Workunits", NULL, NULL, 1);
+            ensureNavLink(*folder, "Browse Workunits", "/WsWorkunits/WUQuery", "Browse Workunits", NULL, NULL, 2);
 
             IPropertyTree *folderQueryset = ensureNavFolder(data, "Query Sets", NULL, NULL, false, 3);
             ensureNavLink(*folderQueryset, "Browse", "/WsWorkunits/WUQuerySets", "Browse Published Queries");

+ 5 - 5
initfiles/etc/DIR_NAME/environment.xml.in

@@ -352,9 +352,9 @@
              processName="EspService"
              schema="esp_service_ecldirect.xsd">
     <Properties bindingType="EclDirectSoapBinding"
-                defaultPort="8008"
+                defaultPort="8010"
                 defaultResourcesBasedn="ou=EclDirectAccess,ou=EspServices,ou=ecl"
-                defaultSecurePort="18008"
+                defaultSecurePort="18010"
                 plugin="ecldirect"
                 type="ecldirect">
      <Authenticate access="Read"
@@ -627,7 +627,7 @@
    <EspBinding defaultForPort="true"
                defaultServiceVersion=""
                name="ecldirect"
-               port="8008"
+               port="8010"
                protocol="http"
                resourcesBasedn="ou=EclDirectAccess,ou=EspServices,ou=ecl"
                service="ecldirect"
@@ -890,9 +890,9 @@
               description="ESP service for running raw ECL queries"
               name="ecldirect">
    <Properties bindingType="EclDirectSoapBinding"
-               defaultPort="8008"
+               defaultPort="8010"
                defaultResourcesBasedn="ou=EclDirectAccess,ou=EspServices,ou=ecl"
-               defaultSecurePort="18008"
+               defaultSecurePort="18010"
                plugin="ecldirect"
                type="ecldirect">
     <Authenticate access="Read"