Bladeren bron

ISSUE #80: Support custom result display via workunit resources
Edit
Add support for defining custom result views to the resource manifest.

Result views are implemented as XSLT stylesheets and are declared in the
manifest using the following syntax.

<Manifest>
<Resource type="XSLT" filename="xslt_dependencies/included.xslt"/>
<Resource type="XSLT" filename="result.xslt" name="displayResults"/>
<Views>
<Results type="XSLT" resource="displayResults" name="display results" />
</Views>
</Manifest>

If the xslt includes other local xslt files they must be added as
addtional resources.

If the xslt includes server side xslt files the include statement can use
a path starting with "/esp/".

Signed-off-by: Anthony Fishbeck Anthony.Fishbeck@lexisnexis.com

Anthony Fishbeck 13 jaren geleden
bovenliggende
commit
45c7bab688

+ 1 - 0
CMakeLists.txt

@@ -141,6 +141,7 @@ add_subdirectory (common/roxiehelper)
 add_subdirectory (common/roxiemanager)
 add_subdirectory (common/thorhelper)
 add_subdirectory (common/workunit)
+add_subdirectory (common/wuwebview)
 add_subdirectory (dali/base)
 add_subdirectory (dali/dafilesrv)
 add_subdirectory (dali/dalidiag)

+ 4 - 4
common/dllserver/thorplugin.cpp

@@ -389,11 +389,11 @@ extern DLLSERVER_API bool getEmbeddedWorkUnitXML(ILoadedDllEntry *dll, StringBuf
     return decompressResource(len, data, xml);
 }
 
-extern DLLSERVER_API bool getEmbeddedSoapInfoXML(ILoadedDllEntry *dll, StringBuffer &xml)
+extern DLLSERVER_API bool getEmbeddedManifestXML(ILoadedDllEntry *dll, StringBuffer &xml)
 {
     size32_t len = 0;
     const void * data = NULL;
-    if (!dll->getResource(len, data, "SOAPINFO", 1000))
+    if (!dll->getResource(len, data, "MANIFEST", 1000))
         return false;
     return decompressResource(len, data, xml);
 }
@@ -418,9 +418,9 @@ extern DLLSERVER_API bool getWorkunitXMLFromFile(const char *filename, StringBuf
     return getResourceXMLFromFile(filename, "WORKUNIT", 1000, xml);
 }
 
-extern DLLSERVER_API bool getSoapInfoXMLFromFile(const char *filename, StringBuffer &xml)
+extern DLLSERVER_API bool getManifestXMLFromFile(const char *filename, StringBuffer &xml)
 {
-    return getResourceXMLFromFile(filename, "SOAPINFO", 1000, xml);
+    return getResourceXMLFromFile(filename, "MANIFEST", 1000, xml);
 }
 
 

+ 2 - 2
common/dllserver/thorplugin.hpp

@@ -37,11 +37,11 @@ interface ILoadedDllEntry : extends IInterface
 extern DLLSERVER_API ILoadedDllEntry * createDllEntry(const char *name, bool isGlobal, const IFileIO *dllFile);
 extern DLLSERVER_API ILoadedDllEntry * createExeDllEntry(const char *name);
 extern DLLSERVER_API bool getEmbeddedWorkUnitXML(ILoadedDllEntry *dll, StringBuffer &xml);
-extern DLLSERVER_API bool getEmbeddedSoapInfoXML(ILoadedDllEntry *dll, StringBuffer &xml);
+extern DLLSERVER_API bool getEmbeddedManifestXML(ILoadedDllEntry *dll, StringBuffer &xml);
 extern DLLSERVER_API bool checkEmbeddedWorkUnitXML(ILoadedDllEntry *dll);
 extern DLLSERVER_API bool getResourceXMLFromFile(const char *filename, const char *type, unsigned id, StringBuffer &xml);
 extern DLLSERVER_API bool getWorkunitXMLFromFile(const char *filename, StringBuffer &xml);
-extern DLLSERVER_API bool getSoapInfoXMLFromFile(const char *filename, StringBuffer &xml);
+extern DLLSERVER_API bool getManifestXMLFromFile(const char *filename, StringBuffer &xml);
 
 extern DLLSERVER_API bool decompressResource(size32_t len, const void *data, StringBuffer &result);
 extern DLLSERVER_API void compressResource(MemoryBuffer & compressed, size32_t len, const void *data);

+ 1 - 1
common/fileview2/fileview.hpp

@@ -230,7 +230,7 @@ extern "C" FILEVIEW_API unsigned getResultXml(IStringVal & ret, INewResultSet *
 
 extern "C" FILEVIEW_API unsigned getResultCursorBin(MemoryBuffer & ret, IResultSetCursor * cursor, unsigned start=0, unsigned count=0);
 extern "C" FILEVIEW_API unsigned getResultBin(MemoryBuffer & ret, INewResultSet * cursor, unsigned start=0, unsigned count=0);
-extern "C" FILEVIEW_API IStringVal& getFullWorkUnitResultsXML(const char *user, const char *pw, const IConstWorkUnit *wu, IStringVal &str, bool inclschema, WUExceptionSeverity minSeverity=ExceptionSeverityInformation);
+extern "C" FILEVIEW_API IStringVal& getFullWorkUnitResultsXML(const char *user, const char *pw, const IConstWorkUnit *wu, IStringVal &str, bool inclschema, WUExceptionSeverity minSeverity=ExceptionSeverityInformation, bool noroot=false);
 
 extern FILEVIEW_API void startRemoteDataSourceServer(const char * queue, const char * cluster);
 extern FILEVIEW_API void stopRemoteDataSourceServer();

+ 6 - 3
common/fileview2/fvresultset.cpp

@@ -3262,11 +3262,13 @@ extern "C" FILEVIEW_API unsigned getResultBin(MemoryBuffer & ret, INewResultSet
 }
 
 
-extern "C" FILEVIEW_API IStringVal& getFullWorkUnitResultsXML(const char *username, const char *password, const IConstWorkUnit *cw, IStringVal &str, bool inclschema, WUExceptionSeverity minSeverity)
+extern "C" FILEVIEW_API IStringVal& getFullWorkUnitResultsXML(const char *username, const char *password, const IConstWorkUnit *cw, IStringVal &str, bool inclschema, WUExceptionSeverity minSeverity, bool noroot)
 {
     SCMStringBuffer wuid;
     cw->getWuid(wuid);
-    StringBuffer result("<Result>");
+    StringBuffer result;
+    if (!noroot)
+        result.append("<Result>");
 
     Owned<IConstWUExceptionIterator> exceptions = &cw->getExceptions();
     ForEach(*exceptions)
@@ -3311,7 +3313,8 @@ extern "C" FILEVIEW_API IStringVal& getFullWorkUnitResultsXML(const char *userna
             result.append("<Exception><Source>System</Source><Message>Query aborted by operator</Message></Exception>");
             break;
     }
-    result.append("</Result>");
+    if (!noroot)
+        result.append("</Result>");
     str.set(result.str());
     return str;
 }

+ 54 - 0
common/wuwebview/CMakeLists.txt

@@ -0,0 +1,54 @@
+################################################################################
+#    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/>.
+################################################################################
+
+
+# Component: wuwebview
+#####################################################
+# Description:
+# ------------
+#    Cmake Input File for wuwebview
+#####################################################
+
+project( wuwebview )
+
+set (    SRCS
+         wuwebview.cpp
+         wuwebview.hpp
+         wuweberror.hpp
+    )
+
+include_directories (
+         ./../../common/workunit
+         ./../../common/fileview2
+         ./../../system/include
+         ./../../common/dllserver
+         ./../../system/jlib
+         ./../../system/xmllib
+    )
+
+HPCC_ADD_LIBRARY( wuwebview SHARED ${SRCS} )
+set_target_properties(wuwebview PROPERTIES
+    COMPILE_FLAGS -D_USRDLL
+    DEFINE_SYMBOL WUWEBVIEW_EXPORTS )
+install ( TARGETS wuwebview DESTINATION ${OSSDIR}/lib )
+target_link_libraries ( wuwebview
+         jlib
+         workunit
+         fileview2
+         dllserver
+         xmllib
+    )

+ 9 - 0
common/wuwebview/sourcedoc.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+<section>
+    <title>common/wuwebview</title>
+
+    <para>
+        The common/wuwebview directory contains the sources for the common/wuwebview library.
+    </para>
+</section>

+ 30 - 0
common/wuwebview/wuweberror.hpp

@@ -0,0 +1,30 @@
+/*##############################################################################
+
+    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 WUERROR_HPP
+#define WUERROR_HPP
+
+#include "jexcept.hpp"
+
+/* Errors can occupy range 5100..5199 */
+
+#define WUWEBERR_WorkUnitNotFound               5500
+#define WUWEBERR_ManifestNotFound               5501
+#define WUWEBERR_ViewResourceNotFound           5502
+
+#endif

+ 458 - 0
common/wuwebview/wuwebview.cpp

@@ -0,0 +1,458 @@
+/*##############################################################################
+
+    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/>.
+############################################################################## */
+
+#include "jlib.hpp"
+#include "jexcept.hpp"
+#include "jptree.hpp"
+#include "workunit.hpp"
+#include "dllserver.hpp"
+#include "thorplugin.hpp"
+#include "xslprocessor.hpp"
+#include "fileview.hpp"
+
+#include "wuwebview.hpp"
+#include "wuweberror.hpp"
+
+class WuExpandedResultBuffer : public CInterface, implements IPTreeNotifyEvent
+{
+public:
+    IMPLEMENT_IINTERFACE;
+
+    WuExpandedResultBuffer(const char *queryname) : name(queryname), datasetLevel(0), finalized(false)
+    {
+        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><");
+        if (queryname)
+            buffer.append(queryname);
+        buffer.append("Response><Results><Result>");
+    }
+
+    void appendResults(IConstWorkUnit *wu, const char *username, const char *pw)
+    {
+        StringBufferAdaptor resultXML(buffer);
+        getFullWorkUnitResultsXML(username, pw, wu, resultXML, false, ExceptionSeverityError, true);
+    }
+
+    void appendSingleResult(IConstWorkUnit *wu, const char *resultname, const char *username, const char *pw)
+    {
+        SCMStringBuffer wuid;
+        StringBufferAdaptor resultXML(buffer);
+        Owned<IResultSetFactory> factory = getResultSetFactory(username, pw);
+        Owned<INewResultSet> nr = factory->createNewResultSet(wu->getWuid(wuid).str(), 0, resultname);
+        getResultXml(resultXML, nr.get(), resultname, 0, 0, NULL);
+    }
+
+    void appendDatasetsFromXML(const char *xml)
+    {
+        assertex(!finalized);
+        Owned<IPullXMLReader> reader = createPullXMLStringReader(xml, *this);
+        reader->load();
+    }
+
+    void appendDatasetXML(const char *xml)
+    {
+        assertex(!finalized);
+        buffer.append(xml);
+    }
+
+    void append(const char *value)
+    {
+        assertex(!finalized);
+        buffer.append(value);
+    }
+
+    void appendSchemaResource(IPropertyTree &res, ILoadedDllEntry &loadedDll)
+    {
+        if (res.getPropInt("@seq", -1)>=0 && res.hasProp("@id"))
+        {
+            int id = res.getPropInt("@id");
+            size32_t len = 0;
+            const void *data = NULL;
+            if (loadedDll.getResource(len, data, "RESULT_XSD", (unsigned) id) && len>0)
+            {
+                StringBuffer decompressed;
+                decompressResource(len, data, decompressed);
+                buffer.append("<XmlSchema name=\"").append(res.queryProp("@name")).append("\">");
+                buffer.append(decompressed.str());
+                buffer.append("</XmlSchema>");
+            }
+        }
+    }
+
+    void appendManifestSchemas(IPropertyTree &manifest, ILoadedDllEntry &loadedDll)
+    {
+        assertex(!finalized);
+        Owned<IPropertyTreeIterator> iter = manifest.getElements("Resource[@type='RESULT_XSD']");
+        ForEach(*iter)
+            appendSchemaResource(iter->query(), loadedDll);
+    }
+
+    void appendManifestResultSchema(IPropertyTree &manifest, const char *resultname, ILoadedDllEntry &loadedDll)
+    {
+        assertex(!finalized);
+        VStringBuffer xpath("Resource[@name='%s']", resultname);
+        Owned<IPropertyTreeIterator> iter = manifest.getElements(xpath.str());
+        ForEach(*iter)
+        {
+            const char *type=iter->query().queryProp("@type");
+            if (type && strieq(type, "RESULT_XSD"))
+                appendSchemaResource(iter->query(), loadedDll);
+        }
+    }
+
+    virtual void beginNode(const char *tag, offset_t startOffset)
+    {
+        if (streq("Dataset", tag))
+            datasetLevel++;
+        if (datasetLevel)
+            buffer.append('<').append(tag);
+    }
+
+    virtual void newAttribute(const char *name, const char *value)
+    {
+        if (datasetLevel)
+            buffer.append(' ').append(name+1).append("=\"").append(value).append('\"');
+    }
+    virtual void beginNodeContent(const char *tag)
+    {
+        if (datasetLevel)
+            buffer.append('>');
+    }
+    virtual void endNode(const char *tag, unsigned length, const void *value, bool binary, offset_t endOffset)
+    {
+        if (datasetLevel)
+        {
+            if (length)
+            {
+                if (binary)
+                    JBASE64_Encode(value, length, buffer);
+                else
+                    buffer.append((const char *)value);
+            }
+            buffer.append("</").append(tag).append('>');
+            if (streq("Dataset", tag))
+                datasetLevel--;
+        }
+    }
+    StringBuffer &finalize()
+    {
+        if (!finalized)
+        {
+            buffer.appendf("</Result></Results></%sResponse>", name.sget());
+            finalized=true;
+        }
+        return buffer;
+    }
+
+    const char *str()
+    {
+        finalize();
+        return buffer.str();
+    }
+
+    size32_t length()
+    {
+        finalize();
+        return buffer.length();
+    }
+public:
+    StringBuffer buffer;
+    StringAttr name;
+    bool finalized;
+private:
+    int datasetLevel;
+};
+
+inline bool isAbsoluteXalanPath(const char *path)
+{
+    if (!path||!*path)
+        return false;
+    return isPathSepChar(path[0])||((path[1]==':')&&(isPathSepChar(path[2])));
+}
+
+class WuWebView : public CInterface,
+    implements IWuWebView,
+    implements IIncludeHandler
+{
+public:
+    IMPLEMENT_IINTERFACE;
+    WuWebView(IConstWorkUnit &wu, const char *queryname, const char *wdir, bool mapEspDir) :
+        manifestIncludePathsSet(false), dir(wdir), mapEspDirectories(mapEspDir)
+    {
+        name.set(queryname);
+        load(wu);
+    }
+
+    WuWebView(const char *wuid, const char *queryname, const char *wdir, bool mapEspDir) :
+        manifestIncludePathsSet(false), dir(wdir), mapEspDirectories(mapEspDir)
+    {
+        name.set(queryname);
+        load(wuid);
+    }
+
+    void load(IConstWorkUnit &wu);
+    void load(const char *wuid);
+    IPropertyTree *ensureManifest();
+
+    virtual void getResultViewNames(StringArray &names);
+    virtual void renderResults(const char *viewName, const char *xml, StringBuffer &html);
+    virtual void renderResults(const char *viewName, StringBuffer &html);
+    virtual void renderSingleResult(const char *viewName, const char *resultname, StringBuffer &html);
+    virtual void applyResultsXSLT(const char *filename, const char *xml, StringBuffer &out);
+    virtual void applyResultsXSLT(const char *filename, StringBuffer &out);
+    virtual StringBuffer &aggregateResources(const char *type, StringBuffer &content);
+
+    void renderExpandedResults(const char *viewName, StringBuffer &expanded, StringBuffer &out);
+
+    void appendResultSchemas(WuExpandedResultBuffer &buffer);
+    void getResultXSLT(const char *viewName, StringBuffer &xslt, StringBuffer &abspath);
+    void getResource(IPropertyTree *res, StringBuffer &content);
+    void getResource(const char *name, StringBuffer &content, StringBuffer &abspath);
+
+    void calculateResourceIncludePaths();
+    virtual bool getInclude(const char *includename, MemoryBuffer &includebuf, bool &pathOnly);
+
+protected:
+    Owned<IConstWorkUnit> wu;
+    Owned<ILoadedDllEntry> loadedDll;
+    Owned<IPropertyTree> manifest;
+    SCMStringBuffer name;
+    bool mapEspDirectories;
+    bool manifestIncludePathsSet;
+    StringAttr dir;
+    StringAttr username;
+    StringAttr pw;
+};
+
+IPropertyTree *WuWebView::ensureManifest()
+{
+    if (!manifest)
+    {
+        StringBuffer xml;
+        manifest.setown(getEmbeddedManifestXML(loadedDll, xml) ? createPTreeFromXMLString(xml.str()) : createPTree());
+    }
+    return manifest.get();
+}
+
+void WuWebView::calculateResourceIncludePaths()
+{
+    if (!manifestIncludePathsSet)
+    {
+        Owned<IPropertyTreeIterator> iter = ensureManifest()->getElements("Resource[@filename]");
+        ForEach(*iter)
+        {
+            const char *filename = iter->query().queryProp("@filename");
+            StringBuffer abspath;
+            if (isAbsoluteXalanPath(filename))
+                abspath.append(filename);
+            else
+            {
+                StringBuffer relpath(dir.get());
+                relpath.append(filename);
+                makeAbsolutePath(relpath.str(), abspath);
+            }
+            iter->query().setProp("@res_include_path", abspath.str());
+        }
+        manifestIncludePathsSet=true;
+    }
+}
+
+bool WuWebView::getInclude(const char *includename, MemoryBuffer &includebuf, bool &pathOnly)
+{
+    int len=strlen(includename);
+    if (len<8)
+        return false;
+    //eliminate "file://"
+    includename+=7;
+    //eliminate extra '/' for windows absolute paths
+    if (len>9 && includename[2]==':')
+        includename++;
+    StringBuffer relpath;
+    if (mapEspDirectories && !strnicmp(includename, "/esp/", 5))
+        relpath.append(dir.get()).append(includename+=5);
+    else
+        relpath.append(includename); //still correct for OS
+    StringBuffer abspath;
+    makeAbsolutePath(relpath.str(), abspath);
+
+    VStringBuffer xpath("Resource[@res_include_path='%s']", abspath.str());
+    IPropertyTree *res = manifest->queryPropTree(xpath.str());
+    if (res)
+    {
+        StringBuffer xslt;
+        getResource(res, xslt);
+        includebuf.append(xslt.str());
+    }
+    else if (checkFileExists(abspath.str()))
+    {
+        Owned <IFile> f = createIFile(abspath.str());
+        Owned <IFileIO> fio = f->open(IFOread);
+        read(fio, 0, (size32_t) f->size(), includebuf);
+    }
+    //esp looks in two places for path starting with "xslt/"
+    else if (mapEspDirectories && !strncmp(includename, "xslt/", 5))
+    {
+        StringBuffer relpath(dir.get());
+        relpath.append("smc_").append(includename);;
+        makeAbsolutePath(relpath.str(), abspath.clear());
+        if (checkFileExists(abspath.str()))
+        {
+            Owned <IFile> f = createIFile(abspath.str());
+            Owned <IFileIO> fio = f->open(IFOread);
+            read(fio, 0, (size32_t) f->size(), includebuf);
+        }
+    }
+    return true;
+}
+
+void WuWebView::getResultViewNames(StringArray &names)
+{
+    Owned<IPropertyTreeIterator> iter = ensureManifest()->getElements("Views/Results[@name]");
+    ForEach(*iter)
+    names.append(iter->query().queryProp("@name"));
+}
+
+void WuWebView::getResource(IPropertyTree *res, StringBuffer &content)
+{
+    if (res->hasProp("@id"))
+    {
+        int id = res->getPropInt("@id");
+        size32_t len = 0;
+        const void *data = NULL;
+        if (loadedDll->getResource(len, data, res->queryProp("@type"), (unsigned) id) && len>0)
+            decompressResource(len, data, content);
+    }
+}
+
+void WuWebView::getResource(const char *name, StringBuffer &content, StringBuffer &includepath)
+{
+    VStringBuffer xpath("Resource[@name='%s']", name);
+    IPropertyTree *res = ensureManifest()->queryPropTree(xpath.str());
+    calculateResourceIncludePaths();
+    includepath.append(res->queryProp("@res_include_path"));
+    if (res)
+        getResource(res, content);
+}
+
+StringBuffer &WuWebView::aggregateResources(const char *type, StringBuffer &content)
+{
+    VStringBuffer xpath("Resource[@type='%s']", type);
+    Owned<IPropertyTreeIterator> iter = ensureManifest()->getElements(xpath.str());
+    ForEach(*iter)
+    getResource(&iter->query(), content);
+    return content;
+}
+
+void WuWebView::getResultXSLT(const char *viewName, StringBuffer &xslt, StringBuffer &abspath)
+{
+    VStringBuffer xpath("Views/Results[@name='%s']/@resource", viewName);
+    const char *resource = ensureManifest()->queryProp(xpath.str());
+    if (resource)
+        getResource(resource, xslt, abspath);
+}
+
+void WuWebView::renderExpandedResults(const char *viewName, StringBuffer &expanded, StringBuffer &out)
+{
+    StringBuffer xslt;
+    StringBuffer rootpath;
+    getResultXSLT(viewName, xslt, rootpath);
+    if (!xslt.length())
+        throw MakeStringException(WUWEBERR_ViewResourceNotFound, "Result view %s not found", viewName);
+    Owned<IXslTransform> t = getXslProcessor()->createXslTransform();
+    t->setIncludeHandler(this);
+    t->setXslSource(xslt.str(), xslt.length(), rootpath.str());
+    t->setXmlSource(expanded.str(), expanded.length());
+    t->transform(out);
+}
+
+void WuWebView::renderResults(const char *viewName, const char *xml, StringBuffer &out)
+{
+    WuExpandedResultBuffer buffer(name.str());
+    buffer.appendDatasetsFromXML(xml);
+    buffer.appendManifestSchemas(*ensureManifest(), *loadedDll);
+    renderExpandedResults(viewName, buffer.finalize(), out);
+}
+
+void WuWebView::renderResults(const char *viewName, StringBuffer &out)
+{
+    WuExpandedResultBuffer buffer(name.str());
+    buffer.appendResults(wu, username.get(), pw.get());
+    buffer.appendManifestSchemas(*ensureManifest(), *loadedDll);
+    renderExpandedResults(viewName, buffer.finalize(), out);
+}
+
+void WuWebView::renderSingleResult(const char *viewName, const char *resultname, StringBuffer &out)
+{
+    WuExpandedResultBuffer buffer(name.str());
+    buffer.appendManifestResultSchema(*ensureManifest(), resultname, *loadedDll);
+    buffer.appendSingleResult(wu, resultname, username.get(), pw.get());
+    renderExpandedResults(viewName, buffer.finalize(), out);
+}
+
+void WuWebView::applyResultsXSLT(const char *filename, const char *xml, StringBuffer &out)
+{
+    WuExpandedResultBuffer buffer(name.str());
+    buffer.appendDatasetsFromXML(xml);
+    buffer.appendManifestSchemas(*ensureManifest(), *loadedDll);
+
+    Owned<IXslTransform> t = getXslProcessor()->createXslTransform();
+    t->setIncludeHandler(this);
+    t->setXslSource(filename);
+    t->setXmlSource(buffer.str(), buffer.length());
+    t->transform(out);
+}
+
+void WuWebView::applyResultsXSLT(const char *filename, StringBuffer &out)
+{
+    SCMStringBuffer xml;
+    getFullWorkUnitResultsXML(username.get(), pw.get(), wu, xml, false, ExceptionSeverityError);
+    applyResultsXSLT(filename, xml.str(), out);
+}
+
+void WuWebView::load(IConstWorkUnit &cwu)
+{
+    wu.set(&cwu);
+    if (!name.length())
+    {
+        wu->getJobName(name);
+        name.s.replace(' ','_');
+    }
+    Owned<IConstWUQuery> q = wu->getQuery();
+    SCMStringBuffer dllname;
+    q->getQueryDllName(dllname);
+    loadedDll.setown(queryDllServer().loadDll(dllname.str(), DllLocationAnywhere));
+}
+
+void WuWebView::load(const char *wuid)
+{
+    Owned<IWorkUnitFactory> factory = getWorkUnitFactory();
+    Owned<IConstWorkUnit> wu = factory->openWorkUnit(wuid, false);
+    if (!wu)
+        throw MakeStringException(WUWEBERR_WorkUnitNotFound, "Workunit not found %s", wuid);
+    load(*wu);
+}
+
+extern WUWEBVIEW_API IWuWebView *createWuWebView(IConstWorkUnit &wu, const char *queryname, const char *dir, bool mapEspDirectories)
+{
+    return new WuWebView(wu, queryname, dir, mapEspDirectories);
+}
+
+extern WUWEBVIEW_API IWuWebView *createWuWebView(const char *wuid, const char *queryname, const char *dir, bool mapEspDirectories)
+{
+    return new WuWebView(wuid, queryname, dir, mapEspDirectories);
+}
+

+ 46 - 0
common/wuwebview/wuwebview.hpp

@@ -0,0 +1,46 @@
+/*##############################################################################
+
+    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 WUWEBVIEW_INCL
+#define WUWEBVIEW_INCL
+
+#ifdef _WIN32
+    #ifdef WUWEBVIEW_EXPORTS
+        #define WUWEBVIEW_API __declspec(dllexport)
+    #else
+        #define WUWEBVIEW_API __declspec(dllimport)
+    #endif
+#else
+    #define WUWEBVIEW_API
+#endif
+
+interface IWuWebView : extends IInterface
+{
+    virtual void getResultViewNames(StringArray &names)=0;
+    virtual void renderResults(const char *viewName, const char *xml, StringBuffer &html)=0;
+    virtual void renderResults(const char *viewName, StringBuffer &html)=0;
+    virtual void renderSingleResult(const char *viewName, const char *resultname, StringBuffer &html)=0;
+    virtual void applyResultsXSLT(const char *file, const char *xml, StringBuffer &html)=0;
+    virtual void applyResultsXSLT(const char *file, StringBuffer &html)=0;
+    virtual StringBuffer &aggregateResources(const char *type, StringBuffer &content)=0;
+};
+
+extern WUWEBVIEW_API IWuWebView *createWuWebView(IConstWorkUnit &wu, const char *queryname, const char*dir, bool mapEspDir);
+extern WUWEBVIEW_API IWuWebView *createWuWebView(const char *wuid, const char *queryname, const char*dir, bool mapEspDir);
+
+#endif

+ 2 - 0
esp/eclwatch/ws_XSLT/wuid.xslt

@@ -481,6 +481,8 @@
             function getSectionLoadUrl(Section)
             {
               var sectionUrl = '';
+              if (Section=="Results")
+                sectionUrl += '&IncludeResultsViewNames=1';
               for(i=0; i < sections.length;i++)
               {
                 sectionUrl += '&Include' + sections[i];

+ 7 - 0
esp/eclwatch/ws_XSLT/wuidcommon.xslt

@@ -1017,6 +1017,7 @@
   </xsl:template>
   <xsl:template match="ECLResult">
     <xsl:variable name="rowclass" select="position() mod 2" />
+    <xsl:variable name="position" select="position()" />
       <tr id="ECL_Result_{position()}" class="{$rowclass}">
       <td>
         <xsl:choose>
@@ -1049,6 +1050,12 @@
               <xsl:value-of select="Value"/>
             </a>
           </td>
+          <xsl:variable name="resultname" select="Name"/>
+          <xsl:for-each select="/WUInfoResponse/ResultViews/View">
+            <td>
+              <a href="javascript:void(0);" onclick="download(document.getElementById('ECL_Result_{$position}'), '/WsWorkunits/WUResultView?Wuid={$wuid}&amp;ResultName={$resultname}&amp;ViewName={.}');return false;"><xsl:value-of select="."/></a>
+            </td>
+          </xsl:for-each>
           <td>
             <a href="javascript:void(0);" onclick="download(document.getElementById('ECL_Result_{position()}'), '/WsWorkunits/WUResultBin?Format=zip&amp;Wuid={$wuid}&amp;Sequence={Link}');return false;">.zip</a>
           </td>

+ 20 - 2
esp/scm/ws_workunits.ecm

@@ -521,13 +521,14 @@ ESPrequest WUInfoRequest
     [min_ver("1.16")] bool IncludeGraphs(true);
     [min_ver("1.16")] bool IncludeSourceFiles(true);
     [min_ver("1.16")] bool IncludeResults(true);
+    [min_ver("1.34")] bool IncludeResultsViewNames(false);
     [min_ver("1.16")] bool IncludeVariables(true);
     [min_ver("1.16")] bool IncludeTimers(true);
     [min_ver("1.16")] bool IncludeDebugValues(true);
     [min_ver("1.16")] bool IncludeApplicationValues(true);
     [min_ver("1.16")] bool IncludeWorkflows(true);
     [min_ver("1.16")] bool SuppressResultSchemas(false);
-   [min_ver("1.25")] string ThorSlaveIP;
+    [min_ver("1.25")] string ThorSlaveIP;
 };
 
 
@@ -537,6 +538,7 @@ ESPresponse [exceptions_inline] WUInfoResponse
     int  AutoRefresh(0);
    bool CanCompile;
    [min_ver("1.25")] string ThorSlaveIP;
+   [min_ver("1.34")] ESParray<string, View> ResultViews;
 }; 
 
 ESPrequest WUResultSummaryRequest
@@ -627,6 +629,21 @@ ESPresponse [exceptions_inline,http_encode(0)] WUResultResponse
     string Result;
 };
 
+ESPrequest WUResultViewRequest
+{
+    string Wuid;
+    string ViewName;
+    int    Sequence;
+    string ResultName;
+};
+
+ESPresponse [exceptions_inline] WUResultViewResponse
+{
+    string Wuid;
+    string ViewName;
+    [http_content("text/html")] string Result;
+};
+
 ESPrequest WUClusterJobQueueXLSRequest
 {
     string Cluster;
@@ -1046,7 +1063,7 @@ ESPresponse [exceptions_inline] WUQuerySetActionAliasesResponse
 
 
 ESPservice [
-    version("1.33"), default_client_version("1.33"),
+    version("1.34"), default_client_version("1.34"),
     noforms,exceptions_inline("./smc_xslt/exceptions.xslt"),use_method_name] WsWorkunits
 {
     ESPuses ESPStruct ECLException;
@@ -1074,6 +1091,7 @@ ESPservice [
     ESPmethod [resp_xsl_default("/esp/xslt/graph_gvc.xslt")]     WUGVCGraphInfo(WUGVCGraphInfoRequest, WUGVCGraphInfoResponse);
     ESPmethod[description("Stub for Ajax GVC Graph."), help(""), resp_xsl_default("/esp/xslt/GvcGraph.xslt")] GVCAjaxGraph(GVCAjaxGraphRequest, GVCAjaxGraphResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/result.xslt")]        WUResult(WUResultRequest, WUResultResponse);
+    ESPmethod WUResultView(WUResultViewRequest, WUResultViewResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/wuid_jobs.xslt")]     WUJobList(WUJobListRequest, WUJobListResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/wuaction_results.xslt")] WUAction(WUActionRequest, WUActionResponse); 
     ESPmethod [resp_xsl_default("/esp/xslt/scheduledwus.xslt")] WUShowScheduled(WUShowScheduledRequest, WUShowScheduledResponse); 

+ 2 - 0
esp/services/ws_ecl/CMakeLists.txt

@@ -51,6 +51,7 @@ include_directories (
          ./../../../system/security/securesocket 
          ./../../../system/include 
          ./../../../common/workunit 
+         ./../../../common/wuwebview
          ./../../../roxie/roxieclient 
          ./../../../common/fileview2 
          ./../../../dali/base 
@@ -76,6 +77,7 @@ target_link_libraries ( ws_ecl
          eclrtl 
          deftype 
          workunit 
+         wuwebview
          jhtree 
          hql 
          fileview2 

+ 52 - 65
esp/services/ws_ecl/ws_ecl_service.cpp

@@ -1336,45 +1336,40 @@ int CWsEclBinding::getGenForm(IEspContext &context, CHttpRequest* request, CHttp
     StringBuffer page;
     Owned<IXslProcessor> xslp = getXslProcessor();
 
-    // get schema
-    StringBuffer schema;
-    //schema.loadFile("files/Corp1.xsd");
+    Owned<IWuWebView> web = createWuWebView(*wsinfo.wu.get(), wsinfo.queryname.get(), getCFD(), true);
+
+    StringBuffer v;
+    StringBuffer formxml("<FormInfo>");
+    appendXMLTag(formxml, "WUID", wsinfo.wuid.sget());
+    appendXMLTag(formxml, "QuerySet", wsinfo.qsetname.sget());
+    appendXMLTag(formxml, "QueryName", wsinfo.queryname.sget());
+    appendXMLTag(formxml, "ClientVersion", v.appendf("%g",context.getClientVersion()).str());
+    appendXMLTag(formxml, "RequestElement", v.clear().append(wsinfo.queryname).append("Request").str());
+    appendXMLTag(formxml, "Help", web->aggregateResources("HELP", v.clear()).str());
+    appendXMLTag(formxml, "Info", web->aggregateResources("INFO", v.clear()).str());
 
     context.addOptions(ESPCTX_ALL_ANNOTATION);
-    getSchema(schema, context, request, wsinfo);
-    DBGLOG("Schema: %s", schema.str());
+    getSchema(formxml, context, request, wsinfo);
+
+    StringArray views;
+    web->getResultViewNames(views);
+    formxml.append("<CustomViews>");
+    ForEachItemIn(i, views)
+        appendXMLTag(formxml, "Result", views.item(i));
+    formxml.append("</CustomViews>");
+    formxml.append("</FormInfo>");
 
     Owned<IXslTransform> xform = xslp->createXslTransform();
 
     StringBuffer xslfile(getCFD());
     xform->setXslSource(xslfile.append("./xslt/wsecl3_form.xsl").str());
-    xform->setXmlSource(schema.str(), schema.length()+1);
-
-    // params
-    xform->setStringParameter("serviceName", wsinfo.qsetname.sget());
-    xform->setStringParameter("queryPath", wsinfo.qsetname.sget());
-    StringBuffer version;
-    version.appendf("%g",context.getClientVersion());
-    xform->setStringParameter("serviceVersion", version.str());
-    xform->setStringParameter("methodName", wsinfo.queryname.sget());
-    xform->setStringParameter("wuid", wsinfo.wuid.sget());
+    xform->setXmlSource(formxml.str(), formxml.length()+1);
 
     // pass params to form (excluding form and __querystring)
     StringBuffer params;
     if (!getUrlParams(context.queryRequestParameters(),params))
         params.appendf("%cver_=%g",(params.length()>0) ? '&' : '?', context.getClientVersion());
     xform->setStringParameter("queryParams", params.str());
-
-    StringBuffer tmp,escaped;
-    wsinfo.getWsResource("HELP", tmp);
-
-    escapeSingleQuote(tmp,escaped);
-    xform->setStringParameter("methodHelp", escaped.str());
-
-    wsinfo.getWsResource("INFO", tmp.clear());
-    escapeSingleQuote(tmp,escaped.clear());
-    xform->setStringParameter("methodDesc", escaped.str());
-
     xform->setParameter("formOptionsAccess", "1");
     xform->setParameter("includeSoapTest", "1");
 
@@ -1392,10 +1387,6 @@ int CWsEclBinding::getGenForm(IEspContext &context, CHttpRequest* request, CHttp
         }
     }
     xform->setParameter("noDefaultValue", formInitialized ? "1" : "0");
-    StringBuffer requestlabel(wsinfo.queryname.sget());
-    requestlabel.append("Request");
-    xform->setStringParameter("requestLabel", requestlabel.str());
-    xform->setStringParameter("requestElement", requestlabel.str());
 
     xform->transform(page);
     response->setContentType("text/html");
@@ -1796,7 +1787,7 @@ void CWsEclBinding::addParameterToWorkunit(IWorkUnit * workunit, IConstWUResult
 }
 
 
-int CWsEclBinding::submitWsEclWorkunit(IEspContext & context, WsWuInfo &wsinfo, const char *xml, StringBuffer &respxml)
+int CWsEclBinding::submitWsEclWorkunit(IEspContext & context, WsWuInfo &wsinfo, const char *xml, StringBuffer &out, const char *viewname, const char *xsltname)
 {
     Owned <IWorkUnitFactory> factory = getSecWorkUnitFactory(*context.querySecManager(), *context.queryUser());
     Owned <IWorkUnit> workunit = factory->createWorkUnit(NULL, "wsecl", context.queryUserId());
@@ -1850,10 +1841,23 @@ int CWsEclBinding::submitWsEclWorkunit(IEspContext & context, WsWuInfo &wsinfo,
     int wutimeout = 300000;
     if (waitForWorkUnitToComplete(wuid.str(), wutimeout))
     {
-        Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid.str(), false);
-        StringBufferAdaptor result(respxml);
-        getFullWorkUnitResultsXML(context.queryUserId(), context.queryPassword(), cw.get(), result, false, ExceptionSeverityError);
-        cw.clear();
+        if (viewname)
+        {
+            Owned<IWuWebView> web = createWuWebView(wuid.str(), wsinfo.queryname.get(), getCFD(), true);
+            web->renderResults(viewname, out);
+        }
+        else if (xsltname)
+        {
+            Owned<IWuWebView> web = createWuWebView(wuid.str(), wsinfo.queryname.get(), getCFD(), true);
+            web->applyResultsXSLT(xsltname, out);
+        }
+        else
+        {
+            Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid.str(), false);
+            StringBufferAdaptor result(out);
+            getFullWorkUnitResultsXML(context.queryUserId(), context.queryPassword(), cw.get(), result, false, ExceptionSeverityError);
+            cw.clear();
+        }
     }
     else
     {
@@ -2041,7 +2045,7 @@ int CWsEclBinding::onSubmitQueryOutputXML(IEspContext &context, CHttpRequest* re
     return 0;
 }
 
-int CWsEclBinding::onSubmitQueryOutputTables(IEspContext &context, CHttpRequest* request, CHttpResponse* response, WsWuInfo &wsinfo)
+int CWsEclBinding::onSubmitQueryOutputView(IEspContext &context, CHttpRequest* request, CHttpResponse* response, WsWuInfo &wsinfo)
 {
     StringBuffer soapmsg;
 
@@ -2052,10 +2056,14 @@ int CWsEclBinding::onSubmitQueryOutputTables(IEspContext &context, CHttpRequest*
 
     StringBuffer output;
     StringBuffer status;
+    StringBuffer html;
 
     SCMStringBuffer clustertype;
     wsinfo.wu->getDebugValue("targetclustertype", clustertype);
 
+    StringBuffer xsltfile(getCFD());
+    xsltfile.append("xslt/wsecl3_result.xslt");
+    const char *view = context.queryRequestParameters()->queryProp("view");
     if (strieq(clustertype.str(), "roxie"))
     {
         const char *addr = wsecl->roxies->queryProp(wsinfo.qsetname.sget());
@@ -2072,43 +2080,22 @@ int CWsEclBinding::onSubmitQueryOutputTables(IEspContext &context, CHttpRequest*
         Owned<IHttpClient> httpclient = httpctx->createHttpClient(NULL, url);
 
         httpclient->sendRequest("POST", "text/xml", soapmsg, output, status);
+        Owned<IWuWebView> web = createWuWebView(*wsinfo.wu, NULL, getCFD(), true);
+        if (!view)
+            web->applyResultsXSLT(xsltfile.str(), output.str(), html);
+        else
+            web->renderResults(view, output.str(), html);
     }
     else
     {
-        submitWsEclWorkunit(context, wsinfo, soapmsg.str(), output);
-    }
-
-    StringBuffer sourcexml;
-    sourcexml.appendf("<?xml version=\"1.0\" encoding=\"UTF-8\"?><%sResponse><Results><Result>", wsinfo.queryname.sget());
-
-    getResultsXmlBody(output.str(), sourcexml);
-
-    Owned<IPropertyTreeIterator> schemas = wsinfo.getResultSchemas();
-    if(schemas.get())
-    {
-        ForEach(*schemas)
-        {
-            IPropertyTree *resultSchema = schemas->query().queryPropTree("xs:schema");
-            if (!resultSchema)
-                resultSchema = schemas->query().queryPropTree("Schema");
-            if (resultSchema)
-            {
-                sourcexml.append("<XmlSchema name=\"").append(schemas->query().queryProp("@name")).append("\">");
-                toXML(resultSchema, sourcexml);
-                sourcexml.append("</XmlSchema>");
-            }
-        }
+        submitWsEclWorkunit(context, wsinfo, soapmsg.str(), html, view, xsltfile.str());
     }
-    sourcexml.appendf("</Result></Results></%sResponse>", wsinfo.queryname.sget());
-
-    StringBuffer html;
-    xsltTransform(sourcexml.str(), sourcexml.length(), "./xslt/wsecl3_result.xslt", NULL, html);
 
     response->setContent(html.str());
     response->setContentType("text/html; charset=utf-8");
     response->setStatus("200 OK");
     response->send();
-    
+
     return 0;
 }
 
@@ -2406,7 +2393,7 @@ int CWsEclBinding::onGet(CHttpRequest* request, CHttpResponse* response)
             splitLookupInfo(parms, thepath, wuid, qs, qid);
             WsWuInfo wsinfo(wuid.str(), qs.str(), qid.str(), context->queryUserId(), context->queryPassword());
 
-            return onSubmitQueryOutputTables(*context, request, response, wsinfo);
+            return onSubmitQueryOutputView(*context, request, response, wsinfo);
         }
         else if (!stricmp(methodName.str(), "example"))
         {

+ 2 - 2
esp/services/ws_ecl/ws_ecl_service.hpp

@@ -167,9 +167,9 @@ public:
     void getSoapMessage(StringBuffer& soapmsg, IEspContext &context, CHttpRequest* request, WsWuInfo &wsinfo, unsigned flags);
     int onGetSoapBuilder(IEspContext &context, CHttpRequest* request, CHttpResponse* response,  WsWuInfo &wsinfo);
     int onSubmitQueryOutputXML(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    WsWuInfo &wsinfo);
-    int onSubmitQueryOutputTables(IEspContext &context, CHttpRequest* request, CHttpResponse* response, WsWuInfo &wsinfo);
+    int onSubmitQueryOutputView(IEspContext &context, CHttpRequest* request, CHttpResponse* response, WsWuInfo &wsinfo);
 
-    int submitWsEclWorkunit(IEspContext & context, WsWuInfo &wsinfo, const char *xml, StringBuffer &respxml);
+    int submitWsEclWorkunit(IEspContext & context, WsWuInfo &wsinfo, const char *xml, StringBuffer &out, const char *viewname=NULL, const char *xsltname=NULL);
 
     void addParameterToWorkunit(IWorkUnit * workunit, IConstWUResult &vardef, IResultSetMetaData &metadef, const char *varname, IPropertyTree *valtree);
     

+ 1 - 20
esp/services/ws_ecl/wswuinfo.cpp

@@ -34,21 +34,8 @@ WsWuInfo::WsWuInfo(const char *wuid_, const char *qset, const char *qname, const
 
 bool WsWuInfo::getWsResource(const char *name, StringBuffer &out)
 {
-/*
-    SCMStringBuffer res;
-    Owned<IConstWUWebServicesInfo> wsres = wu->getWebServicesInfo();
-    if (wsres)
-    {
-        wsres->getInfo(name, res);
-        out.append(res.s);
-    }
-
-    return (res.length()>0);
-*/
     if (strieq(name, "SOAP"))
     {
-
-
         out.appendf("<message name=\"%s\">", queryname.sget());
         IConstWUResultIterator &vars = wu->getVariables();
         Owned<IResultSetFactory> resultSetFactory(getResultSetFactory(username, password));
@@ -58,13 +45,7 @@ bool WsWuInfo::getWsResource(const char *name, StringBuffer &out)
             SCMStringBuffer varname;
             var.getResultName(varname);
             int seq = var.getResultSequence();
-    /*
-            ResultFormatRaw = 0,
-    ResultFormatXml = 1,
-    ResultFormatXmlSet = 2,
-    ResultFormatCsv = 3,
-    ResultFormatSize = 4
-    */
+
             WUResultFormat fmt = var.getResultFormat();
 
             SCMStringBuffer eclschema;

+ 1 - 0
esp/services/ws_ecl/wswuinfo.hpp

@@ -20,6 +20,7 @@
 #define _WS_ECL_WUINFO_HPP__
 
 #include "workunit.hpp"
+#include "wuwebview.hpp"
 
 class WsWuInfo : public CInterface
 {

+ 2 - 0
esp/services/ws_workunits/CMakeLists.txt

@@ -53,6 +53,7 @@ include_directories (
          ./../../../system/security/shared   
          ./../../../system/include 
          ./../../../common/workunit 
+         ./../../../common/wuwebview
          ./../../../ecl/schedulectrl 
          ./../../clients 
          ./../../../common/fileview2 
@@ -85,6 +86,7 @@ target_link_libraries ( ws_workunits
          eclrtl 
          deftype 
          workunit 
+         wuwebview
          SMCLib 
          schedulectrl 
          roxiecommlib 

+ 33 - 2
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -47,6 +47,7 @@
 #include "zcrypt.hpp"
 #endif
 #include "exception_util.hpp"
+#include "wuwebview.hpp"
 
 #define     File_Cpp "cpp"
 #define     File_ThorLog "ThorLog"
@@ -2981,6 +2982,18 @@ bool CWsWorkunitsEx::onWUResult(IEspContext &context, IEspWUResultRequest &req,
    return true;
 }
 
+bool CWsWorkunitsEx::onWUResultView(IEspContext &context, IEspWUResultViewRequest &req, IEspWUResultViewResponse &resp)
+{
+    ensureWorkunitAccess(context, req.getWuid(), SecAccess_Read);
+    Owned<IWuWebView> wv = createWuWebView(req.getWuid(), NULL, getCFD(), true);
+    StringBuffer html;
+    wv->renderSingleResult(req.getViewName(), req.getResultName(), html);
+    resp.setResult(html.str());
+    resp.setResult_mimetype("text/html");
+    return true;
+}
+
+
 bool CWsWorkunitsEx::getResultSchemas(IConstWUResult &r, IArrayOf<IEspECLSchemaItem>& schemas)
 {
     SCMStringBuffer schema;
@@ -5194,7 +5207,7 @@ void CWsWorkunitsEx::getHelpFiles(double version, IConstWUQuery* query, WUFileTy
     }
 }
 
-void CWsWorkunitsEx::getInfo(IEspContext &context,const char* wuid0,IEspECLWorkunit *info, bool bTruncateEclTo64k, bool IncludeExceptions, bool IncludeGraphs, bool IncludeSourceFiles, bool IncludeResults, bool IncludeVariables, bool IncludeTimers, bool IncludeDebugValues, bool IncludeApplicationValues, bool IncludeWorkflows, bool SuppressResultSchemas)
+void CWsWorkunitsEx::getInfo(IEspContext &context,const char* wuid0,IEspECLWorkunit *info, bool bTruncateEclTo64k, bool IncludeExceptions, bool IncludeGraphs, bool IncludeSourceFiles, bool IncludeResults, bool IncludeVariables, bool IncludeTimers, bool IncludeDebugValues, bool IncludeApplicationValues, bool IncludeWorkflows, bool SuppressResultSchemas, StringArray *resultViews)
 {
     StringBuffer wuidStr = wuid0;
     wuidStr.trim();
@@ -5941,6 +5954,24 @@ void CWsWorkunitsEx::getInfo(IEspContext &context,const char* wuid0,IEspECLWorku
         if (workflowsException)
             info->setWorkflowsDesc(msg);
     }
+    try
+    {
+        if (resultViews)
+        {
+            Owned<IWuWebView> wv = createWuWebView(*wu, NULL, NULL, false);
+            wv->getResultViewNames(*resultViews);
+        }
+    }
+    catch(IException* e)
+    {
+        applicationValuesException = true;
+
+        StringBuffer eMsg;
+        e->errorMessage(eMsg);
+        ERRLOG("%s", eMsg.str()); //log original exception
+        e->Release();
+    }
+
 }
 
 void CWsWorkunitsEx::addSubFiles(IPropertyTreeIterator* f, IEspECLSourceFile* eclSuperFile, StringArray& fileNames)
@@ -6338,7 +6369,7 @@ bool CWsWorkunitsEx::onWUInfo(IEspContext &context, IEspWUInfoRequest &req, IEsp
         {
             try
             {
-                getInfo(context, req.getWuid(), &resp.updateWorkunit(), req.getTruncateEclTo64k(), req.getIncludeExceptions(), req.getIncludeGraphs(), req.getIncludeSourceFiles(), req.getIncludeResults(), req.getIncludeVariables(), req.getIncludeTimers(), req.getIncludeDebugValues(), req.getIncludeApplicationValues(), req.getIncludeWorkflows(), req.getSuppressResultSchemas());
+                getInfo(context, req.getWuid(), &resp.updateWorkunit(), req.getTruncateEclTo64k(), req.getIncludeExceptions(), req.getIncludeGraphs(), req.getIncludeSourceFiles(), req.getIncludeResults(), req.getIncludeVariables(), req.getIncludeTimers(), req.getIncludeDebugValues(), req.getIncludeApplicationValues(), req.getIncludeWorkflows(), req.getSuppressResultSchemas(), req.getIncludeResultsViewNames() ? &resp.getResultViews() : NULL);
 
                 int n = resp.getWorkunit().getStateID();
                 if (n == WUStateCompiling || n == WUStateCompiled || n == WUStateScheduled || n == WUStateSubmitted 

+ 2 - 1
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -256,6 +256,7 @@ public:
     bool onWUInfoDetails(IEspContext &context, IEspWUInfoRequest &req, IEspWUInfoResponse &resp);
     bool onWUFile(IEspContext &context,IEspWULogFileRequest &req, IEspWULogFileResponse &resp);
     bool onWUResult(IEspContext &context,IEspWUResultRequest &req, IEspWUResultResponse &resp);
+    bool onWUResultView(IEspContext &context, IEspWUResultViewRequest &req, IEspWUResultViewResponse &resp);
     bool onWUResultSummary(IEspContext &context, IEspWUResultSummaryRequest &req, IEspWUResultSummaryResponse &resp);
     bool onWUResultBin(IEspContext &context, IEspWUResultBinRequest &req, IEspWUResultBinResponse &resp);
    bool onWUGraphInfo(IEspContext &context,IEspWUGraphInfoRequest &req, IEspWUGraphInfoResponse &resp);
@@ -314,7 +315,7 @@ private:
     void getWorkunitThorLog(IEspContext &context, const char* type, const char *wuid,MemoryBuffer& log);
     void getWorkunitThorSlaveLog(IEspContext &context, const char *wuid,const char *slaveip,MemoryBuffer& buf);
     void getResult(IEspContext &context,IConstWUResult &r,IArrayOf<IEspECLResult>& results, const char* wuid = NULL, bool SuppressSchemas=false);
-    void getInfo(IEspContext &context,const char* wuid,IEspECLWorkunit *info, bool bTruncateEclTo64k, bool IncludeExceptions=true, bool IncludeGraphs=true, bool IncludeSourceFiles=true, bool IncludeResults=true, bool IncludeVariables=true, bool IncludeTimers=true, bool IncludeDebugValues=true, bool IncludeApplicationValues=true, bool IncludeWorkflows=true, bool SuppressSchemas=false);
+    void getInfo(IEspContext &context,const char* wuid,IEspECLWorkunit *info, bool bTruncateEclTo64k, bool IncludeExceptions=true, bool IncludeGraphs=true, bool IncludeSourceFiles=true, bool IncludeResults=true, bool IncludeVariables=true, bool IncludeTimers=true, bool IncludeDebugValues=true, bool IncludeApplicationValues=true, bool IncludeWorkflows=true, bool SuppressSchemas=false, StringArray *resultViews=NULL);
     bool getInfoFromSasha(IEspContext &context,const char *sashaServer,const char* wuid,IEspECLWorkunit *info);
     void getResultView(INewResultSet* result, __int64 start, unsigned& count,__int64& total,IStringVal& resname,bool raw,MemoryBuffer& buf);
     void getFileResults(IEspContext &context, const char* logicalName, const char* cluster,__int64 start, unsigned& count,__int64& total,IStringVal& resname,bool raw,MemoryBuffer& buf);

+ 24 - 17
esp/xslt/wsecl3_form.xsl

@@ -15,23 +15,21 @@
     <!-- ===============================================================================
   parameters 
   ================================================================================ -->
-    <xsl:param name="serviceName" select="'Risk_Indicators'"/>
-    <xsl:param name="queryPath" select="'Risk_Indicators'"/>
-    <xsl:param name="methodName" select="'Risk_Indicators.InstantID'"/>
-    <xsl:param name="wuid"/>
-    <xsl:param name="methodHelp" select="'..Help..'"/>
-    <xsl:param name="methodDesc" select="'..Desc..'"/>
-    <xsl:param name="requestLabel" select="'Risk_IndicatorsInstantIDRequest'"/>
-    <xsl:param name="serviceVersion" select="'1.0'"/>
     <xsl:param name="queryParams" select="''"/>
     <xsl:param name="formOptionsAccess" select="1"/>
     <xsl:param name="noDefaultValue" select="0"/> 
     <xsl:param name="includeSoapTest" select="1"/>
-    <xsl:param name="schemaRoot" select="xsd:schema"/>
+    <xsl:param name="schemaRoot" select="/FormInfo/xsd:schema"/>
     <xsl:param name="esdl_links" select="0"/>
-
-    <xsl:param name="requestElement" select="'Risk_Indicators.InstantIDRequest'"/>
     
+    <xsl:variable name="queryPath" select="/FormInfo/QuerySet"/>
+    <xsl:variable name="methodName" select="/FormInfo/QueryName"/>
+    <xsl:variable name="wuid" select="/FormInfo/WUID"/>
+    <xsl:variable name="methodHelp" select="/FormInfo/Help"/>
+    <xsl:variable name="methodDesc" select="/FormInfo/Info"/>
+    <xsl:variable name="serviceVersion" select="/FormInfo/Version"/>
+    <xsl:variable name="requestLabel" select="/FormInfo/RequestElement"/>
+    <xsl:variable name="requestElement" select="/FormInfo/RequestElement"/>
     
     <!-- ===============================================================================
   global settings 
@@ -46,8 +44,11 @@
     <!-- ================================================================================
    The main template: produce the html (and call GenerateRequestForm for request input)
   ================================================================================ -->
-  
-    <!--xsl:variable name="schemaRoot" select="ViewFormResponse/xsd/xsd:schema"/ -->
+
+    <xsl:template match="FormInfo">
+        <xsl:apply-templates select="xsd:schema"/>
+    </xsl:template>
+    <!--xsl:variable name="" select="ViewFormResponse/xsd/xsd:schema"/ -->
     <xsl:template match="xsd:schema">
           <xsl:if test="$verbose">
               noDefaultValue: <xsl:value-of select="$noDefaultValue"/>
@@ -84,7 +85,7 @@
         
         <html>
             <head>
-                <title>WsECL Service form</title>
+                <title>WsECL Service formx</title>
                 <link rel="shortcut icon" href="/esp/files/img/affinity_favicon_1.ico" />
                 <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/fonts/fonts-min.css" />
                 <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
@@ -139,9 +140,8 @@ function setESPFormAction()  // reqType: 0: regular form, 1: soap, 2: form param
     var form = document.forms['esp_form'];
     if (!form)  return false;
 
-    var actionpath="]]><xsl:value-of select="concat('/WsEcl/submit/wuid/', $wuid, '?qset=', $queryPath, '&amp;qname=', $methodName, '&amp;display')"/> <![CDATA[";
-
     var actval = document.getElementById('submit_type');
+    var actionpath = "/jserror";
     if (actval && actval.value)
     {
         if (actval.value=="esp_soap")
@@ -154,6 +154,10 @@ function setESPFormAction()  // reqType: 0: regular form, 1: soap, 2: form param
             actionpath = "]]><xsl:value-of select="concat('/WsEcl/forms/roxiexml/', $queryPath, '/', $methodName)"/><![CDATA[";
         else if (actval.value=="run_xslt")
             actionpath = "]]><xsl:value-of select="concat('/WsEcl/xslt/query/', $queryPath, '/', $methodName)"/><![CDATA[";
+        else if (actval.value=="xml")
+            actionpath = "]]><xsl:value-of select="concat('/WsEcl/submit/wuid/', $wuid, '?qset=', $queryPath, '&amp;qname=', $methodName, '&amp;view=xml&amp;display')"/> <![CDATA[";
+        else
+            actionpath= "]]><xsl:value-of select="concat('/WsEcl/xslt/query/', $queryPath, '/', $methodName, '?view=')"/><![CDATA["+actval.value;
     }
     //alert("actionpath = " + actionpath);
     var dest = document.getElementById('esp_dest');
@@ -192,7 +196,7 @@ function setESPFormAction()  // reqType: 0: regular form, 1: soap, 2: form param
                         <td height="23">
                             <font color="#efefef">
                                 <b>
-                                    <xsl:value-of select="$serviceName"/>
+                                    <xsl:value-of select="/FormInfo/QuerySet"/>
                                     <xsl:if test="number($serviceVersion)>0">
                                         <xsl:value-of select="concat(' [Version ', $serviceVersion, ']')"/>
                                     </xsl:if>
@@ -286,6 +290,9 @@ function setESPFormAction()  // reqType: 0: regular form, 1: soap, 2: form param
                 <tr class='commands'>
                   <td align='left'>
                     <select id="submit_type" name="submit_type_">
+                        <xsl:for-each select="/FormInfo/CustomViews/Result">
+                            <option><xsl:attribute name="value"><xsl:value-of select="."/></xsl:attribute><xsl:value-of select="."/></option>
+                        </xsl:for-each>
                         <option value="run_xslt">OUTPUT TABLES</option>
                         <option value="xml">OUTPUT XML</option>
                         <option value="esp_soap">SOAP TEST</option>

+ 2 - 0
system/include/errorlist.h

@@ -44,6 +44,8 @@
 #define WORKUNIT_ERROR_START    5000
 #define WORKUNIT_ERROR_END      5099
 
+#define WUWEB_ERROR_START       5500
+#define WUWEB_ERROR_END         5599
 
 #define REMOTE_ERROR_START      8000    // dafilesrv etc - see common/remote/remoteerr.hpp
 #define REMOTE_ERROR_END        8099

+ 7 - 0
system/jlib/jstring.hpp

@@ -362,6 +362,13 @@ inline const char *encodeUtf8XML(const char *x, StringBuffer &ret, unsigned flag
     return encodeXML(x, ret, flags, len, true);
 }
 
+inline StringBuffer &appendXMLTag(StringBuffer &xml, const char *tag, const char *value, unsigned flags=0, unsigned len=(unsigned)-1, bool utf8=true)
+{
+    xml.append('<').append(tag).append('>');
+    encodeXML(value, xml, flags, len, utf8);
+    return xml.append("</").append(tag).append('>');
+}
+
 extern jlib_decl void decodeCppEscapeSequence(StringBuffer & out, const char * in, bool errorIfInvalid);
 extern jlib_decl bool strToBool(const char * text);
 extern jlib_decl bool strToBool(size_t len, const char * text);

+ 2 - 2
system/xmllib/xslprocessor.cpp

@@ -552,9 +552,9 @@ int CXslTransform::setXslSource(const char *pszFileName)
     return 0;
 }
 
-int CXslTransform::setXslSource(const char *pszBuffer, unsigned int nSize)
+int CXslTransform::setXslSource(const char *pszBuffer, unsigned int nSize, const char *rootpath)
 {
-    m_xslsource.setown(new CXslSource(pszBuffer, nSize, m_sourceResolver?m_sourceResolver->getIncludeHandler():NULL));
+    m_xslsource.setown(new CXslSource(pszBuffer, nSize, m_sourceResolver?m_sourceResolver->getIncludeHandler():NULL, rootpath));
 
     return 0;
 }

+ 1 - 1
system/xmllib/xslprocessor.hpp

@@ -53,7 +53,7 @@ public:
     // setXslSource - specifies the source of the XSLT "script" to use
     //
     virtual int setXslSource(const char *pszFileName) = 0;
-    virtual int setXslSource(const char *pszBuffer, unsigned int nSize) = 0;
+    virtual int setXslSource(const char *pszBuffer, unsigned int nSize, const char *rootpath=NULL) = 0;
  
     // setResultTarget - Specifies where the post transform data is to be placed
     // must be set before calling parameterless variety of transform

+ 13 - 6
system/xmllib/xslprocessor.ipp

@@ -122,8 +122,7 @@ public:
                     m_includes->append(pbuf.str());
                 }
                 StringBuffer path;
-                char firstc = buf.toByteArray()[0];
-                if(firstc != '/' && firstc != PATHSEPCHAR)
+                if(!isAbsolutePath((const char *)buf.toByteArray()))
                 {
                     char baseurl[1025];
                     GetCurrentDirectory(1024, baseurl);
@@ -154,6 +153,7 @@ private:
     XalanTransformer m_XalanTransformer;
     IO_Type m_sourcetype;
     StringAttr m_filename;
+    StringAttr m_rootpath;
     StringBuffer m_xsltext;
     XalanCompiledStylesheet* m_CompiledStylesheet;
     Owned<MemSourceResolver>  m_sourceResolver;
@@ -173,7 +173,7 @@ public:
             setIncludeHandler(handler);
     }
 
-    CXslSource(const char* buf, int len, IIncludeHandler* handler) : m_XalanTransformer()
+    CXslSource(const char* buf, int len, IIncludeHandler* handler, const char *rootpath = NULL) : m_XalanTransformer()
     {
         m_xsltext.append(len, buf);
         m_sourcetype = IO_TYPE_BUFFER;
@@ -181,6 +181,8 @@ public:
 
         if(handler)
             setIncludeHandler(handler);
+        if (rootpath)
+            m_rootpath.set(rootpath);
     }
 
     virtual ~CXslSource()
@@ -250,8 +252,13 @@ public:
                     
                     char baseurl[1031];
                     strcpy(baseurl, URLPREFIX);
-                    GetCurrentDirectory(1024, baseurl + strlen(URLPREFIX));
-                    strcpy(baseurl+strlen(baseurl), PATHSEPSTR);
+                    if (m_rootpath)
+                        strcpy(baseurl, m_rootpath.sget());
+                    else
+                    {
+                        GetCurrentDirectory(1024, baseurl + strlen(URLPREFIX));
+                        strcpy(baseurl+strlen(baseurl), PATHSEPSTR);
+                    }
 
                     xslinput.setSystemId(XalanDOMString(baseurl).c_str());
                     m_XalanTransformer.compileStylesheet((const XSLTInputSource&)xslinput, (const XalanCompiledStylesheet*&)m_CompiledStylesheet);
@@ -495,7 +502,7 @@ public:
     virtual int setXmlSource(const char *pszFileName);
     virtual int setXmlSource(const char *pszBuffer, unsigned int nSize);
     virtual int setXslSource(const char *pszFileName);
-    virtual int setXslSource(const char *pszBuffer, unsigned int nSize);
+    virtual int setXslSource(const char *pszBuffer, unsigned int nSize, const char *rootpath);
     virtual int setResultTarget(char *pszBuffer, unsigned int nSize);
     virtual int setResultTarget(const char *pszFileName);
     virtual int closeResultTarget();

+ 7 - 7
tools/wuget/wuget.cpp

@@ -26,14 +26,14 @@ void usage(const char *progname)
 
 int main(int argc, char **argv)
 {
-    bool doSoapInfo = false;
+    bool doManifestInfo = false;
     bool doWorkunit = false;
     for (int i = 1; i < argc; i++)
     {
         if (argv[i][0]=='-')
         {
-            if (strcmp(argv[i], "-s")==0)
-                doSoapInfo = true;
+            if (strcmp(argv[i], "-m")==0)
+                doManifestInfo = true;
             else if (strcmp(argv[i], "-w")==0)
                 doWorkunit = true;
             else
@@ -50,7 +50,7 @@ int main(int argc, char **argv)
         if (argv[i][0]!='-')
         {
             filesSeen++;
-            if (doWorkunit || !doSoapInfo)
+            if (doWorkunit || !doManifestInfo)
             {
                 StringBuffer xml;
                 if (getWorkunitXMLFromFile(argv[i], xml))
@@ -63,16 +63,16 @@ int main(int argc, char **argv)
                     errors++;
                 }
             }
-            if (doSoapInfo)
+            if (doManifestInfo)
             {
                 StringBuffer xml;
-                if (getSoapInfoXMLFromFile(argv[i], xml))
+                if (getManifestXMLFromFile(argv[i], xml))
                 {
                     printf("%s\n", xml.str());
                 }
                 else
                 {
-                    fprintf(stderr, "Could not load soapInfo from %s\n", argv[i]);
+                    fprintf(stderr, "Could not load manifest from %s\n", argv[i]);
                     errors++;
                 }
             }