From eb9e4362f09bf07892f4b593d0bb100d8e7bfb98 Mon Sep 17 00:00:00 2001 From: Malte Kremer <m.kremer@adito.de> Date: Wed, 2 Jun 2021 11:15:57 +0000 Subject: [PATCH] =?UTF-8?q?1081754/1079943=20Timeshift=20in=20Basic=20?= =?UTF-8?q?=C3=BCbernehmen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- process/DataTimeshift/DataTimeshift.aod | 9 + process/DataTimeshift/process.js | 135 ++++++++++++++ .../DataTimeshift_lib/DataTimeshift_lib.aod | 10 + process/DataTimeshift_lib/process.js | 176 ++++++++++++++++++ .../DatabaseSupplier_lib.aod | 9 + process/DatabaseSupplier_lib/process.js | 77 ++++++++ process/Supplier_lib/Supplier_lib.aod | 9 + process/Supplier_lib/process.js | 27 +++ 8 files changed, 452 insertions(+) create mode 100644 process/DataTimeshift/DataTimeshift.aod create mode 100644 process/DataTimeshift/process.js create mode 100644 process/DataTimeshift_lib/DataTimeshift_lib.aod create mode 100644 process/DataTimeshift_lib/process.js create mode 100644 process/DatabaseSupplier_lib/DatabaseSupplier_lib.aod create mode 100644 process/DatabaseSupplier_lib/process.js create mode 100644 process/Supplier_lib/Supplier_lib.aod create mode 100644 process/Supplier_lib/process.js diff --git a/process/DataTimeshift/DataTimeshift.aod b/process/DataTimeshift/DataTimeshift.aod new file mode 100644 index 0000000000..7ed0045dd2 --- /dev/null +++ b/process/DataTimeshift/DataTimeshift.aod @@ -0,0 +1,9 @@ +<?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.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2"> + <name>DataTimeshift</name> + <majorModelMode>DISTRIBUTED</majorModelMode> + <process>%aditoprj%/process/DataTimeshift/process.js</process> + <variants> + <element>EXECUTABLE</element> + </variants> +</process> diff --git a/process/DataTimeshift/process.js b/process/DataTimeshift/process.js new file mode 100644 index 0000000000..c5cc7fd57e --- /dev/null +++ b/process/DataTimeshift/process.js @@ -0,0 +1,135 @@ +import("system.logging"); +import("system.text"); +import("system.vars"); +import("system.eMath"); +import("Employee_lib"); +import("PermissionCalendar_lib"); +import("system.tools"); +import("system.calendars"); +import("Sql_lib"); +import("system.datetime"); +import("system.db"); +import("system.project"); +import("DataTimeshift_lib"); + +var userTitle = vars.get("$sys.user"); +//only allow the timeshifting-service user to perform this because the process_audit checks the origin of a change +//=> a no-audit alias is not required +if (userTitle != TimeShiftUtils.getTimeShiftingServiceUserTitle()) + throw new Error("[dataTimeshifting_serverProcess]Wrong user started the process. Given user: \"" + userTitle + "\"" + + " but required user is \"" + TimeShiftUtils.getTimeShiftingServiceUserTitle() + "\""); + +//the reference timestamp (=the time when the last time shifting has been performed) is stored in the service users params +var user = tools.getUser(userTitle, tools.PROFILE_FULL); +var referenceTimestamp = user[tools.PARAMS]["timeshiftReferenceTimestamp"]; +if (!referenceTimestamp) + throw new Error("[dataTimeshifting_serverProcess]The given timeshiftReferenceTimestamp paran is empty." + + "Tried to receive the param from user: \"" + userTitle + "\""); + +referenceTimestamp = datetime.toLong(referenceTimestamp, "yyyy-MM-dd'T'HH:mm:ssZ", "UTC"); +var now = datetime.date(); +var timeshiftingTime = eMath.subInt(now, referenceTimestamp); +_getCommonTableShifter().timeshift(timeshiftingTime); + +_getBinariesShifter().timeshift(timeshiftingTime); + + +//calendars are different and need a custom treatment, since the calendars.***-methods are used +var calendarShifter = _getCalendarShifter(); +if (calendarShifter) //is null when calendarbackend != DB +{ + calendarShifter.timeshift(timeshiftingTime); +} +//Initial reference point is: "2020-09-16T00:00:00+0000"; +user[tools.PARAMS]["timeshiftReferenceTimestamp"] = datetime.toDate(now, "yyyy-MM-dd'T'HH:mm:ssZ", "UTC"); +tools.updateUser(user); +/** + * Creates a TimeShifter object for the "Data_alias"-alias and returns the object. + * ASYS_*** tables are skipped as well as the liquibase-tables "DATABASECHANGELOG" and "DATABASECHANGELOGLOCK" + */ +function _getCommonTableShifter() +{ + var alias = "Data_alias"; + var timeshifter = new TimeShifter(alias); + + var excludedTables = ["DATABASECHANGELOG", "DATABASECHANGELOGLOCK"]; + var tables = db.getTables(alias); + var tablesToShift = timeshifter.getTablesToShift(); + tables.forEach(function (tableName){ + if (!tableName.startsWith("ASYS_") && !excludedTables.includes(tableName)) + tablesToShift.add(tableName); + }); + timeshifter.setTablesToShift( tablesToShift); + timeshifter.getTablesToShift().forEach(function(table){ str_log+= "\n"+table; }); + return timeshifter; +} + +/** + * Creates a TimeShifter object for the ASYS_BINARIES and returns that object. + */ +function _getBinariesShifter() +{ + var alias = SqlUtils.getBinariesAlias(); + var timeshifter = new TimeShifter(alias); + timeshifter.getTablesToShift().add("ASYS_BINARIES"); + return timeshifter; +} + +/** + * Creates a TimeShifter object for calendar events and returns it. + * This will only work if the calendar-event backend is database and appopriate grants are set. + */ +function _getCalendarShifter() +{ + var backendType = calendars.getBackendType(); + if (backendType == calendars.BACKEND_DB) + { + var alias = project.getInstanceConfigValue("calendarAlias", null); + //calendars set grant to everyone here + var timeshifter = new TimeShifter(alias); + timeshifter.getTablesToShift().add("ASYS_CALENDARBACKEND"); + timeshifter.getCustomTreatments().set("ASYS_CALENDARBACKEND", function(pTimeDiff){ + var entryQuery = newSelect("ASYS_CALENDARBACKEND.ELEMENTUID, min(ASYS_CALENDARBACKEND.OWNER)", alias) + .from("ASYS_CALENDARBACKEND") + .where("ASYS_CALENDARBACKEND.ENTRYTYPE", calendars.VEVENT) + .and("ASYS_CALENDARBACKEND.CLASSIFICATION", 0) + .and("VCOMPONENT not like '%CATEGORIES:Feiertag%'") + .groupBy("ASYS_CALENDARBACKEND.ELEMENTUID"); + + entryQuery.pageSize(500); + entryQuery.forEachPage(function(pEntries){ + for (var i = 0, l = pEntries.length; i < l; i++) + { + var entry = calendars.getEntry(pEntries[i][0], null, _getTitleCalenderUser(pEntries[i][1]), calendars.VEVENT); + entry[calendars.DTSTART] = String(eMath.addInt(entry[calendars.DTSTART], pTimeDiff)); + entry[calendars.DTEND] = String(eMath.addInt(entry[calendars.DTEND], pTimeDiff)); + calendars.update([entry]); + } + }); + }); + return timeshifter; + } + return null; +} + +/** + * Parses a calendarUser in calendaruser-form (e.g.: "; mailo: hans.mueller@mailing.de;" and returns the adito login name + */ +function _getTitleCalenderUser(pCalendarUser) +{ + var data = text.decodeMS(pCalendarUser) + for ( var i = 0; i < data.length; i++ ) + { + //if login changes we have to check calendarid + if ( data[i].substr(0, "mailto:".length).toUpperCase() == "MAILTO:" ) + { + var user = tools.getUserByAttribute(tools.CALENDARID, [data[i].substr("mailto:".length)]); + if (user != null ) + return user[tools.TITLE]; + } + + if ( data[i].substr(0, 3).toUpperCase() == "CN:" ) + return data[i].substr(3); + } + return ""; +} diff --git a/process/DataTimeshift_lib/DataTimeshift_lib.aod b/process/DataTimeshift_lib/DataTimeshift_lib.aod new file mode 100644 index 0000000000..29d178a8be --- /dev/null +++ b/process/DataTimeshift_lib/DataTimeshift_lib.aod @@ -0,0 +1,10 @@ +<?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.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2"> + <name>DataTimeshift_lib</name> + <majorModelMode>DISTRIBUTED</majorModelMode> + <process>%aditoprj%/process/DataTimeshift_lib/process.js</process> + <alias>Data_alias</alias> + <variants> + <element>LIBRARY</element> + </variants> +</process> diff --git a/process/DataTimeshift_lib/process.js b/process/DataTimeshift_lib/process.js new file mode 100644 index 0000000000..1d32d54279 --- /dev/null +++ b/process/DataTimeshift_lib/process.js @@ -0,0 +1,176 @@ +import("Util_lib"); +import("Sql_lib"); +import("system.translate"); +import("system.SQLTYPES"); +import("system.logging"); +import("system.tools"); +import("system.calendars"); +import("system.datetime"); +import("system.eMath"); +import("system.db"); +import("system.project"); +import("DatabaseSupplier_lib"); +import("Supplier_lib"); + +/** + * Object for helping to shift DATE/TIMESTAMP-databasefields of an alias. + * @param {String} pAlias <p/>database alias for the shifting transactions. Data will be read from this alias and written into this alias + * @throws Error when no alias is provided + */ +function TimeShifter(pAlias) +{ + if (pAlias === undefined) + throw new Error("[TimeShifter.constructor]No alias is defined, but it is required"); + this._alias = pAlias; + this._tablesToUpdate = new Set(); + this._customTreatments = new Map(); + + this._defaulTreatmentFunc = function(pDifferenceMillis, pTablename, pPrimaryKeySupplier, pColumnsSupplier, pColumnTypeSupplier) { + var columnNames = pColumnsSupplier.get(); + var columnTypes = pColumnTypeSupplier.get(); + var pkField = pPrimaryKeySupplier.get(); + var alias = this.getAlias(); + + if (!pkField)//we cannot update if there is no primary key field + return; + + var dateCols = []; + var dateColTypes = []; + for (var i = 0, l = columnNames.length; i < l; i++) + { + if (columnTypes[i] == SQLTYPES.TIMESTAMP || columnTypes[i] == SQLTYPES.DATE) + { + dateCols.push(columnNames[i]); + dateColTypes.push(columnTypes[i]); + } + } + if (dateCols.length == 0) + return; + + var queryFields = [pkField].concat(dateCols); + var dataQuery = newSelect(queryFields, this.getAlias()) + .from(pTablename) + //some databases need a sorting for correct paging + .orderBy(pkField); + + dataQuery.pageSize(500); + dataQuery.forEachPage(function (pData){ + if (pData.length == 0) + return; + var updateStatements = []; + for (var i = 0, l = pData.length; i < l; i++) + { + var vals = pData[i].slice(1);//first values is always the primary key + //reduce the array to an object where the key is the columnname and the value is the target value for the update + vals = vals.reduce(function(pUpdateObject, pValueToAdd, pIndex){ + //empty timestamp should be ignored + if (pValueToAdd == "") + return pUpdateObject; + var column = dateCols[pIndex]; + pUpdateObject[column] = eMath.addInt(pValueToAdd, pDifferenceMillis); + return pUpdateObject; + }, {}); + //if a field is empty it is not added so it can happen that the object is empty when there are no date fields filled + if ( !Utils.isEmpty( vals)) + { + updateStatements.push( + newWhere([pTablename, pkField], pData[i][0], null, null, alias) + .buildUpdateStatement(vals, pTablename) + ); + } + } + if (updateStatements.length > 0) + db.updates(updateStatements, alias); + },null, null, this); + }; +} + +/** + * + * @return {String} returns the alias that is set for the TimeShifter-object + */ +TimeShifter.prototype.getAlias = function(){ + + return this._alias; +}; + +/** + * + * @return {Set} <p/>returns a Set where the tables that shall be shiftet can be added + * + * @example + * var exampleShifter = new TimeShifter("example_dbalias"); + * exampleShifter.getTablesToShift().add("EXAMPLE_TABLE"); + */ +TimeShifter.prototype.getTablesToShift = function(){ + return this._tablesToUpdate; +}; +TimeShifter.prototype.setTablesToShift = function( pTablesToUpdate){ + this._tablesToUpdate = pTablesToUpdate; +} + +/** + * + * @return {Map} <p/>returns a Map where the key is the tablename and the value is a callback function that performs the shifting with the following + * params: + * <ul> + * <li>{String} timeDiff - the difference in milliseconds where the shift has to be performed</li> + * <li>{String} tableName - name of the table that is shifted</li> + * <li>{CachedSupplierWrapper} pkSupplier - a cached "PrimaryKeyNameSupplier" to retrieve the primary key name of the table</li> + * <li>{CachedSupplierWrapper} columnSupplier - a cached "ColumnSupplier" to retrieve all columns of the table</li> + * <li>{CachedSupplierWrapper} columnTypesSupplier - a cached "ColumnTypesSupplier" to retrieve all columntypes of the table</li> + * </ul> + * <p/>You can access the TimeShifter-object via the "this" reference + * + * @example + * var exampleShifter = new TimeShifter("example_dbalias"); + * exampleShifter.getTablesToShift().add("EXAMPLE_TABLE"); + * exampleShifter.getCustomTreatments().set("EXAMPLE_TABLE", function(pTimeDiff, pTable, pPkSupplier, pColumnSupplier, pColumnTypeSupplier){ + * Â Â Â Â var primaryKeyField = pPkSupplier.get(); + * Â Â Â Â //code for shifting ...... + * ); + */ +TimeShifter.prototype.getCustomTreatments = function(){ + return this._customTreatments; +}; + +/** + * After configurating the TimeShifter object the data can be shiftet with the timeshift-function. + * + * @param {String|Number} pOffsetInMillis <p/>The amount of milliseconds for shifting the data, if this is e.g. one day (86400000) every date + * will be shiftet one day in the future + */ +TimeShifter.prototype.timeshift = function (pOffsetInMillis) +{ + var timeDiff = pOffsetInMillis; + this.getTablesToShift().forEach(function (tableName) { + var pkSupplier = new CachedSupplierWrapper(new PrimaryKeyNameSupplier(this.getAlias(), tableName)); + var columnSupplier = new CachedSupplierWrapper(new ColumnSupplier(this.getAlias(), tableName)); + var columnTypesSupplier = new CachedSupplierWrapper(new ColumnTypesSupplier(this.getAlias(), tableName, columnSupplier)); + if (this.getCustomTreatments().has(tableName)) + { + var customTreamentFunc = this.getCustomTreatments().get(tableName); + //assertions + customTreamentFunc.call(this, timeDiff, tableName, pkSupplier, columnSupplier, columnTypesSupplier); + } + else + { + this._defaulTreatmentFunc.call(this, timeDiff, tableName, pkSupplier, columnSupplier, columnTypesSupplier); + } + }, this); + this.setTablesToShift( new Set()) +}; + +/** + * Provides static utility functions for shifting timestamp database ifelds + * Don't instanciate TimeShiftUtils. + */ +function TimeShiftUtils(){} + +/** + * + * @return Title of the user that is used for timeshifting-actions + */ +TimeShiftUtils.getTimeShiftingServiceUserTitle = function (){ + return "timeshiftService"; +} \ No newline at end of file diff --git a/process/DatabaseSupplier_lib/DatabaseSupplier_lib.aod b/process/DatabaseSupplier_lib/DatabaseSupplier_lib.aod new file mode 100644 index 0000000000..a9d5d7e631 --- /dev/null +++ b/process/DatabaseSupplier_lib/DatabaseSupplier_lib.aod @@ -0,0 +1,9 @@ +<?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.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2"> + <name>DatabaseSupplier_lib</name> + <majorModelMode>DISTRIBUTED</majorModelMode> + <process>%aditoprj%/process/DatabaseSupplier_lib/process.js</process> + <variants> + <element>LIBRARY</element> + </variants> +</process> diff --git a/process/DatabaseSupplier_lib/process.js b/process/DatabaseSupplier_lib/process.js new file mode 100644 index 0000000000..f9052970b6 --- /dev/null +++ b/process/DatabaseSupplier_lib/process.js @@ -0,0 +1,77 @@ +import("system.logging"); +import("system.db"); +import("system.project"); + + +/* + * Database supplier lib, supplies a unified object structure for loading table definitions + */ +function AbstractDbTableBasedSupplier(pAlias, pTable) +{ + this._alias = pAlias; + this._table = pTable; +} + +AbstractDbTableBasedSupplier.prototype.get = function() +{ + throw new Error("[AbstractDbTableBasedSupplier.get]Not implemented because it is abstract."); +}; + + +//Supplier for pks +function PrimaryKeyNameSupplier(pAlias, pTable) +{ + AbstractDbTableBasedSupplier.call(this, pAlias, pTable); +} +PrimaryKeyNameSupplier.prototype = Object.create(AbstractDbTableBasedSupplier.prototype); +PrimaryKeyNameSupplier.prototype.constructor = AbstractDbTableBasedSupplier; + +//uses alias definitions to return the primary key name for pTable defined in the constructor +PrimaryKeyNameSupplier.prototype.get = function() +{ + if (!this.hasOwnProperty(this._resultCache)) + { + var struct = project.getAliasDefinitionStructure(this._alias, this._table); + var tableStruct = struct.tables[ this._table.toUpperCase()]; + if (tableStruct == undefined){ + this._resultCache = ""; + logging.log( this._table +"'s alias definition could not be found, primary key could not be established"); + }else + this._resultCache = tableStruct.idColumn || ""; + + } + return this._resultCache; +}; + +//Supplier for column names +function ColumnSupplier(pAlias, pTable) +{ + AbstractDbTableBasedSupplier.call(this, pAlias, pTable); +} +ColumnSupplier.prototype = Object.create(AbstractDbTableBasedSupplier.prototype); +ColumnSupplier.prototype.constructor = AbstractDbTableBasedSupplier; + +//returns all column names of a table pTable defined in the constructor +ColumnSupplier.prototype.get = function() +{ + var columns = db.getColumns(this._table, this._alias); + return columns; +}; + + +//Supplier for column types +function ColumnTypesSupplier(pAlias, pTable, pColumnSupplier) +{ + AbstractDbTableBasedSupplier.call(this, pAlias, pTable); + this._columnSuppier = pColumnSupplier; +} +ColumnTypesSupplier.prototype = Object.create(AbstractDbTableBasedSupplier.prototype); +ColumnTypesSupplier.prototype.constructor = AbstractDbTableBasedSupplier; + +//returns column types for all columns of table pTable defined in the constructor +ColumnTypesSupplier.prototype.get = function() +{ + var columns = this._columnSuppier.get(); + var columnTypes = db.getColumnTypes(this._table, columns, this._alias); + return columnTypes; +}; \ No newline at end of file diff --git a/process/Supplier_lib/Supplier_lib.aod b/process/Supplier_lib/Supplier_lib.aod new file mode 100644 index 0000000000..8bfdde7648 --- /dev/null +++ b/process/Supplier_lib/Supplier_lib.aod @@ -0,0 +1,9 @@ +<?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.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2"> + <name>Supplier_lib</name> + <majorModelMode>DISTRIBUTED</majorModelMode> + <process>%aditoprj%/process/Supplier_lib/process.js</process> + <variants> + <element>LIBRARY</element> + </variants> +</process> diff --git a/process/Supplier_lib/process.js b/process/Supplier_lib/process.js new file mode 100644 index 0000000000..6c4788e913 --- /dev/null +++ b/process/Supplier_lib/process.js @@ -0,0 +1,27 @@ +/* + * Wrapper object to cache and load suppliers (see DatabaseSupplier_lib) + * + */ + +function CachedSupplierWrapper(pSupplier) +{ + //todo: assertion + this._supplier = pSupplier; + this._valueLoaded = false; + this._cache = null; +} + +CachedSupplierWrapper.prototype.get = function() +{ + if (!this._valueLoaded) + { + this._cache = this.getSupplier().get(); + this._valueLoaded = true; + } + return this._cache; +}; + +CachedSupplierWrapper.prototype.getSupplier = function () +{ + return this._supplier; +}; \ No newline at end of file -- GitLab