From 05da2a02bbe5297f0716bbf8607c826efd0d1f93 Mon Sep 17 00:00:00 2001
From: "S.Listl" <S.Listl@SLISTL.aditosoftware.local>
Date: Thu, 12 Mar 2020 14:27:21 +0000
Subject: [PATCH] Webservices for Workflow, SqlBuilder paging functions

(cherry picked from commit 55c8f9d23fb6c31e11250c4bb1dfa684f7e66ddd)
---
 .../recordcontainers/jdito/contentProcess.js  |   4 +-
 process/Attribute_lib/process.js              |  35 ++---
 .../CreateActivity_workflowService.aod        |   1 +
 .../CreateNotification_workflowService.aod    |   1 +
 .../SendEmail_workflowService.aod             |   1 +
 .../SetAttribute_workflowService.aod          |   1 +
 process/Sql_lib/documentation.adoc            |  38 ++++++
 process/Sql_lib/process.js                    | 122 +++++++++++++++++-
 .../UpdateOffer_workflowService.aod           |   1 +
 process/workflowServiceTasks_rest/process.js  |  14 +-
 .../workflowServiceTasks_rest.aod             |   5 +-
 11 files changed, 186 insertions(+), 37 deletions(-)

diff --git a/entity/Employee_entity/recordcontainers/jdito/contentProcess.js b/entity/Employee_entity/recordcontainers/jdito/contentProcess.js
index 6b41c5dc3f..d5f8e7904a 100644
--- a/entity/Employee_entity/recordcontainers/jdito/contentProcess.js
+++ b/entity/Employee_entity/recordcontainers/jdito/contentProcess.js
@@ -20,8 +20,6 @@ else
     users = tools.getUsersByAttribute(tools.ISACTIVE, values, tools.PROFILE_FULL);
 }
 
-var fetchRoles = vars.getString("$local.filter").indexOf("ROLE_FILTER") !== -1;
-
 users = users.map(function (user)
 {
     return [
@@ -37,7 +35,7 @@ users = users.map(function (user)
         user[tools.PARAMS].department,
         "", //password
         "", //confirm_password
-        fetchRoles ? tools.getRoles(user[tools.TITLE]) : [], //for filtering
+        user[tools.ROLENAMES], //for filtering
         EmployeeUtils.sliceUserId(user[tools.NAME])
     ];
 });
diff --git a/process/Attribute_lib/process.js b/process/Attribute_lib/process.js
index 40ba6cd3b0..70c52e8d37 100644
--- a/process/Attribute_lib/process.js
+++ b/process/Attribute_lib/process.js
@@ -1383,7 +1383,7 @@ AttributeRelationQuery.prototype.getSingleAttributeValue = function ()
 AttributeRelationQuery.prototype.getAttributeCount = function ()
 {
     return parseInt(AttributeRelationUtils.getAttributeSqlBuilder("count(*)", this._rowId, this._objectType)
-        .andIfSet("AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", this._attributeIds)
+        .andIfSet("AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", this._attributeIds, SqlBuilder.IN())
         .cell() || 0);
 }
 
@@ -1418,35 +1418,24 @@ AttributeRelationQuery.prototype.insertAttribute = function (pValue, pOmitValida
         maxCount = maxCount[0];
         if (maxCount && maxCount != 0)
         {
-            let timesUsed = new AttributeRelationQuery(this._rowId, this._objectType, attributeId).getAttributeCount();
+            let timesUsed = this.getAttributeCount();
             if (timesUsed >= maxCount)
                 return false;
         }
     }
 
-    var columns = [
-        "AB_ATTRIBUTERELATIONID",
-        "AB_ATTRIBUTE_ID",
-        "OBJECT_ROWID",
-        "OBJECT_TYPE",
-        "DATE_NEW",
-        "USER_NEW"
-    ];
-    var values = [
-        util.getNewUUID(),
-        attributeId,
-        this._rowId,
-        this._objectType,
-        vars.get("$sys.date"),
-        vars.get("$sys.user")
-    ];
+    var attrData = {
+        "AB_ATTRIBUTE_ID" : attributeId,
+        "OBJECT_ROWID" : this._rowId,
+        "OBJECT_TYPE" : this._objectType,
+        "DATE_NEW" : vars.get("$sys.date"),
+        "USER_NEW" : vars.get("$sys.user")
+    };
     var type = AttributeUtil.getAttributeType(attributeId);
     var valueField = AttributeTypeUtil.getDatabaseField(type);
     if (valueField)
-    {
-        columns.push(valueField);
-        values.push(pValue);
-    }
-    db.insertData("AB_ATTRIBUTERELATION", columns, null, values);
+        attrData[valueField] = pValue;
+    
+    new SqlBuilder().insertFields(attrData, "AB_ATTRIBUTERELATION", "AB_ATTRIBUTERELATIONID");
     return true;
 }
\ No newline at end of file
diff --git a/process/CreateActivity_workflowService/CreateActivity_workflowService.aod b/process/CreateActivity_workflowService/CreateActivity_workflowService.aod
index 84cf2710e9..1c0a3458bb 100644
--- a/process/CreateActivity_workflowService/CreateActivity_workflowService.aod
+++ b/process/CreateActivity_workflowService/CreateActivity_workflowService.aod
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
   <name>CreateActivity_workflowService</name>
+  <title>Create activity</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/CreateActivity_workflowService/process.js</process>
   <alias>Data_alias</alias>
diff --git a/process/CreateNotification_workflowService/CreateNotification_workflowService.aod b/process/CreateNotification_workflowService/CreateNotification_workflowService.aod
index 30456b5519..15076d3037 100644
--- a/process/CreateNotification_workflowService/CreateNotification_workflowService.aod
+++ b/process/CreateNotification_workflowService/CreateNotification_workflowService.aod
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
   <name>CreateNotification_workflowService</name>
+  <title>Create notification</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/CreateNotification_workflowService/process.js</process>
   <variants>
diff --git a/process/SendEmail_workflowService/SendEmail_workflowService.aod b/process/SendEmail_workflowService/SendEmail_workflowService.aod
index b83ca696f9..7fbc304aa8 100644
--- a/process/SendEmail_workflowService/SendEmail_workflowService.aod
+++ b/process/SendEmail_workflowService/SendEmail_workflowService.aod
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
   <name>SendEmail_workflowService</name>
+  <title>Send email</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/SendEmail_workflowService/process.js</process>
   <variants>
diff --git a/process/SetAttribute_workflowService/SetAttribute_workflowService.aod b/process/SetAttribute_workflowService/SetAttribute_workflowService.aod
index 89b6c76c71..9d4e32046b 100644
--- a/process/SetAttribute_workflowService/SetAttribute_workflowService.aod
+++ b/process/SetAttribute_workflowService/SetAttribute_workflowService.aod
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
   <name>SetAttribute_workflowService</name>
+  <title>Set attribute</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/SetAttribute_workflowService/process.js</process>
   <alias>Data_alias</alias>
diff --git a/process/Sql_lib/documentation.adoc b/process/Sql_lib/documentation.adoc
index 6bf24551ca..4330b3af21 100644
--- a/process/Sql_lib/documentation.adoc
+++ b/process/Sql_lib/documentation.adoc
@@ -292,6 +292,10 @@ var activitySelect = newSelect("SUBJECT, INFO, personLink.OBJECT_ROWID")
 
 `orderBy(pFields)` adds an order by statement to the SQL code. The parameter can be filled the same way as `.select(pFields)`.
 
+=== union
+
+`union(pSelect)` and `unionAll(pSelect)` can be used to create an union or union all statement. The given parameter can be either a SQL-String or another SqlBuilder with full select clause.
+
 === building the SQL statement
 
 ==== prepared array
@@ -351,6 +355,40 @@ a default of "0" is more convenient than.
 [NOTE]
 You can still use the above `db.` functions outside the SqlBuilder, if you like.
 
+==== paging functions
+
+The following functions support paging:
+
+`.arrayPage()`
+`.tablePage()`
+`.nextTablePage()`
+`.forEachPage()`
+
+The methods `.arrayPage()` and `.tablePage()` are just wrappers for the equivalent `db.`-functions. However, the other functions should make the paging process a bit easier.
+Before using them, you have to set the page size with `.pageSize(pPageSize)` and optionally set the start row with `.startRow(pStartRow)`.
+
+`.nextTablePage()` can be used to iterate over the table pages:
+[source,js]
+----
+mySqlBuilder.pageSize(400);
+var data;
+while (mySqlBuilder.hasMoreRows())
+{
+    data = mySqlBuilder.nextTablePage();
+    ...
+}
+----
+
+You can use `.hasMoreRows()` to check if there are rows left that can be fetched.
+Alternatively you can use `.forEachPage()`, this method requires a callback-function that is called for every table page:
+[source,js]
+----
+mySqlBuilder.pageSize(400)
+    .forEachPage(function (pData)
+    {   
+        ...
+    });
+----
 
 ==== update/delete-functions
 
diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js
index 5f110cfbef..a56abb8304 100644
--- a/process/Sql_lib/process.js
+++ b/process/Sql_lib/process.js
@@ -777,10 +777,10 @@ function newWhereIfSet(pFieldOrCond, pValue, pCondition, pFieldType, pAlias)
 function SqlBuilder (pAlias)
 {
     if(!(this instanceof SqlBuilder)) 
-        throw SqlBuilder.ERROR_INSTANCIATE_WITH_NEW();
+        throw SqlBuilder._ERROR_INSTANCIATE_WITH_NEW();
     this._select = null;
     this._from = null;
-    this._tableName = null; //for update/delete
+    this._tableName = null; //for insert/update/delete
     this._joins = [];
     this._groupBy = null;
     this._having = null;
@@ -788,6 +788,11 @@ function SqlBuilder (pAlias)
     this._unions = [];
     this.alias = pAlias;
     
+    //for paging
+    this._startRow = null;
+    this._pageSize = null;
+    this._hasMoreRows = true;
+    
     this._subselectAlias = null;
     
     this._where = {};
@@ -913,6 +918,15 @@ SqlBuilder._ERROR_UPDATE_VALUES_INVALID = function ()
     return new Error(translate.text("SqlBuilder: The provided values object for updateFields is invalid or is not an object."));
 }
 
+SqlBuilder._ERROR_PAGESIZE_INVALID = function ()
+{
+    return new Error(translate.text("SqlBuilder: The pagesize is not set or is not a number."));
+}
+
+SqlBuilder._ERROR_NOT_A_FUNCTION = function ()
+{
+    return new Error(translate.text("SqlBuilder: The provided callback function is not a function."));
+}
 /**
  * Alternative way of creating a new SqlBuilder object that allows to use
  * methods on it directly without having to put brackets around it
@@ -1009,6 +1023,18 @@ SqlBuilder.prototype.subselectAlias = function(pSubselectAlias)
     return this;
 }
 
+/**
+ * Sets the table that is used for insert/update/delete functions.
+ * 
+ * @param {String} pTable
+ * @return {SqlBuilder} current SqlBuilder object
+ */
+SqlBuilder.prototype.tableName = function (pTable)
+{
+    this._tableName = pTable;
+    return this;
+}
+
 /**
  * Sets the from clause of the sql.<br/>
  * <br/>
@@ -2166,6 +2192,7 @@ SqlBuilder.prototype.clearWhere = function()
  */
 SqlBuilder.prototype._initWhere = function ()
 {
+    //TODO: maybe put conditions in an object/array for better internal object structure
     this._where._sqlStorage = "";
     this._where.preparedValues = [];
     this._where._lastWasOr = false; // save, if the last condition was an OR. For better bracket-placement
@@ -2487,7 +2514,7 @@ SqlBuilder.prototype.insertFields = function (pFieldValues, pTableName, pAutoUid
         throw SqlBuilder._ERROR_UPDATE_VALUES_INVALID;
         
     if (pAutoUidField)
-        pFielsValues[pAutoUidField] = util.getNewUUID();
+        pFieldValues[pAutoUidField] = util.getNewUUID();
     
     var columns = [];
     var values = [];
@@ -2630,8 +2657,8 @@ SqlBuilder.prototype.arrayPage = function(pType, pStartIndex, pRowCount, pExecut
     {
         return db.arrayPage(pType, this.build(),
             (this.alias ? this.alias : db.getCurrentAlias()),
-            pStartIndex,
-            pRowCount,
+            pStartIndex === undefined ? this._startRow : pStartIndex,
+            pRowCount === undefined ? this._pageSize : pRowCount,
             (pTimeout ? pTimeout : -1));
     }
     else
@@ -2680,8 +2707,8 @@ SqlBuilder.prototype.tablePage = function(pStartIndex, pRowCount, pExecuteOnlyIf
     {
         return db.tablePage(this.build(),
             (this.alias ? this.alias : db.getCurrentAlias()),
-            pStartIndex,
-            pRowCount,
+            pStartIndex === undefined ? this._startRow : pStartIndex,
+            pRowCount === undefined ? this._pageSize : pRowCount,
             (pTimeout ? pTimeout : -1));
     }
     else
@@ -2690,6 +2717,87 @@ SqlBuilder.prototype.tablePage = function(pStartIndex, pRowCount, pExecuteOnlyIf
     }
 }
 
+/**
+ * Sets the pagesize for paging
+ * 
+ * @param {Number} pPageSize
+ * @return {SqlBuilder} current SqlBuilder object
+ */
+SqlBuilder.prototype.pageSize = function (pPageSize)
+{
+    this._pageSize = pPageSize;
+    return this;
+}
+
+/**
+ * Sets the start row for paging
+ * 
+ * @param {Number} pStartRow
+ * @return {SqlBuilder} current SqlBuilder object
+ */
+SqlBuilder.prototype.startRow = function (pStartRow)
+{
+    this._startRow = pStartRow;
+    return this;
+}
+
+/**
+ * Executes the SQL and returns the result. The startRow for paging will be increased by the pageSize, so you can use this method
+ * for iterating over the table pages. You can use SqlBuilder.prototype.hasMoreRows() to check if the end of rows was reached.
+ * 
+ * @param {Boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned
+ * @param {Number} [pTimeout=-1]
+ * @return {String[][]} the result of the query
+ */
+SqlBuilder.prototype.nextTablePage = function (pExecuteOnlyIfConditionExists, pTimeout)
+{
+    if (this._pageSize == null || isNaN(this._pageSize))
+        throw SqlBuilder._ERROR_PAGESIZE_INVALID();
+    
+    if (this._startRow == null)
+        this._startRow = 0;
+    
+    if (this._hasMoreRows && this._checkForSelect(pExecuteOnlyIfConditionExists))
+    {
+        var data = this.tablePage(this._startRow, this._pageSize, pExecuteOnlyIfConditionExists, pTimeout);
+        if (data.length < this._pageSize)
+            this._hasMoreRows = false;
+        this._startRow += this._pageSize;
+        return data;
+    }
+    else
+    {
+        this._hasMoreRows = false;
+        return [];
+    }
+}
+
+/**
+ * @return {Boolean} whether there are rows left for paging
+ */
+SqlBuilder.prototype.hasMoreRows = function ()
+{
+    return this._hasMoreRows;
+}
+
+/**
+ * Executes the SQL with paging and executes the given callback-function for every resultset until the last row has been reached or the function
+ * returns false.
+ * 
+ * @param {Function} pCallBackFn CallBack-Function to execute for every page. If the function returns false, the execution will be stopped.
+ * @param {Boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned
+ * @param {Number} [pTimeout=-1]
+ */
+SqlBuilder.prototype.forEachPage = function (pCallBackFn, pExecuteOnlyIfConditionExists, pTimeout)
+{
+    if (typeof pCallBackFn !== "function")
+        throw SqlBuilder._ERROR_NOT_A_FUNCTION();
+    
+    var run = true;
+    while (run && this.hasMoreRows())
+        run = pCallBackFn.call(null, this.nextTablePage(pExecuteOnlyIfConditionExists, pTimeout)) != false;
+}
+
 /**
  * checks if an update /delete statement should be called or not
  * @return {Boolean}
diff --git a/process/UpdateOffer_workflowService/UpdateOffer_workflowService.aod b/process/UpdateOffer_workflowService/UpdateOffer_workflowService.aod
index a58c778e5c..9770a01efb 100644
--- a/process/UpdateOffer_workflowService/UpdateOffer_workflowService.aod
+++ b/process/UpdateOffer_workflowService/UpdateOffer_workflowService.aod
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
   <name>UpdateOffer_workflowService</name>
+  <title>Update offer</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/UpdateOffer_workflowService/process.js</process>
   <alias>Data_alias</alias>
diff --git a/process/workflowServiceTasks_rest/process.js b/process/workflowServiceTasks_rest/process.js
index f713bc0fe1..967a59c5b8 100644
--- a/process/workflowServiceTasks_rest/process.js
+++ b/process/workflowServiceTasks_rest/process.js
@@ -1,3 +1,4 @@
+import("Sql_lib");
 import("system.project");
 import("system.process");
 
@@ -5,13 +6,20 @@ function restget (pRequest)
 {
     let request = JSON.parse(pRequest);
     
-    let serviceTasks = project.getDataModels(project.DATAMODEL_KIND_PROCESS).filter(function (row)
+    let serviceTasks = project.getDataModels(project.DATAMODEL_KIND_PROCESS)
+    .filter(function (row)
     {
         return /.+_workflowService$/.test(row[0]);
+    })
+    .map(function (row)
+    {
+        return {
+            id : row[0],
+            name : row[1] || row[0]
+        };
     });
     
-    request.response.body = JSON.stringify({});
+    request.response.body = JSON.stringify(serviceTasks);
 
     return JSON.stringify(request);
-
 }
diff --git a/process/workflowServiceTasks_rest/workflowServiceTasks_rest.aod b/process/workflowServiceTasks_rest/workflowServiceTasks_rest.aod
index 1222a5a8a5..db543d1d50 100644
--- a/process/workflowServiceTasks_rest/workflowServiceTasks_rest.aod
+++ b/process/workflowServiceTasks_rest/workflowServiceTasks_rest.aod
@@ -3,8 +3,11 @@
   <name>workflowServiceTasks_rest</name>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/workflowServiceTasks_rest/process.js</process>
-  <publishAsWebservice v="false" />
+  <publishAsWebservice v="true" />
   <style>REST</style>
+  <restAcceptedMimeType>application/json</restAcceptedMimeType>
+  <restDeliveredMimeType>application/json</restDeliveredMimeType>
+  <jditoWebserviceUser>flowableIdmService</jditoWebserviceUser>
   <variants>
     <element>EXECUTABLE</element>
   </variants>
-- 
GitLab