    importPackage(java.io);
    importPackage(Packages.de.elo.client.scripting.constants);

    //@include lib_Class.js

    /**
     * Library functions for ELO solution package installation
     *
     * @author ELO Digital Office GmbH
     *
     * @elojc
     * @eloas
     *
     * @requires moment
     * @requires handlebars
     * @requires sol.common.Config
     * @requires sol.common.Template
     * @requires sol.common.IxUtils
     * @requires sol.common.ObjectUtils
     * @requires sol.common.StringUtils
     * @requires sol.common.AsyncUtils
     * @requires sol.common.FileUtils
     * @requires sol.common.ZipUtils
     * @requires sol.common.SordUtils
     * @requires sol.common.RepoUtils
     * @requires sol.common.AclUtils
     * @requires sol.common.WfUtils
     * @requires sol.common.AsUtils
     * @requires sol.common.HttpUtils
     * @requires sol.common.UserUtils
     * @requires sol.common.SordTypeUtils
     * @requires sol.common.ExceptionUtils
     * @requires sol.common.UserProfile
     * @requires sol.common.TranslateTerms
     * @requires sol.dev.DateShiftUtils
     */

    if (typeof globalScope == "undefined") {
      globalScope = {};
    }

    sol.define("sol.dev.install.MultiProperties", {

      /**
       * @cfg {String} filePath
       * FilePath
       */

      /**
       * @cfg {String} language
       * Language
       */

      initialize: function (params) {
        var me = this;

        me.$super("sol.Base", "initialize", [params]);

        if (!me.filePath) {
          throw "Properties file path is empty";
        }
        me.file = new File(me.filePath);

        me.language = me.language || ixConnect.loginResult.clientInfo.language;

        me.defaultProps = me.readProps();
        if (!me.defaultProps) {
          throw "Can't load properties: " + me.filePath;
        }

        me.langProps = me.readProps(me.language);
      },

      /**
       * Returns the localized text
       * @param {String} key Key
       * @return {String} Text
       */
      getText: function (key) {
        var me = this,
            value;

        if (!key) {
          throw "Key is empty";
        }

        if (me.langProps) {
          value = me.langProps.getProperty(key);
        }
        if (!value) {
          value = me.defaultProps.getProperty(key);
        }
        return value ? String(value) : String(key);
      },

      /**
       * @private
       * Reads a properties file
       * @param {String} language Language
       * @return {java.util.Properties}
       */
      readProps: function (language) {
        var me = this,
            props, filePathWithoutExtension, path, extension,
            file, fileInputStream;

        props = new java.util.Properties();

        path = me.filePath;

        if (language) {
          language = String(language).toUpperCase();
          extension = sol.common.FileUtils.getExtension(me.file);
          filePathWithoutExtension = sol.common.FileUtils.removeExtension(me.file);
          path = filePathWithoutExtension + "_" + language + "." + extension;
        }
        file = new File(path);
        if (file.exists()) {
          fileInputStream = new java.io.FileInputStream(path);
          props.load(fileInputStream);

          return props;
        }
      }
    });

    sol.define("sol.dev.install.RepoDataUtils", {
      singleton: true,

      /**
       * Converts the export info to Object
       * @param {String} exportInfo Export info
       * @return {Object} Result
       *
       * Example:
       *
       */
      exportInfoToObject: function (exportInfo) {
        var me = this,
            raw, result, keys, i, j, key, docMaskInfo, maskElements, docMaskName,
            headingKeys, headings, lineExists, docMaskResult;

        result = {
          docMasks: {
          }
        };

        if (!exportInfo) {
          throw "Export info is empty";
        }
        raw = sol.common.StringUtils.parseIniString(exportInfo);
        keys = Object.keys(raw.MASKS);
        for (i = 0; i < keys.length; i++) {
          key = keys[i];
          docMaskInfo = raw["MASK" + key];
          maskElements = me.splitExportInfoLine(docMaskInfo.MaskElms);
          docMaskName = maskElements[6];
          headings = me.splitExportInfoLine(maskElements[11], { separator: "|" });
          headingKeys = me.splitExportInfoLine(docMaskInfo.MaskTextTK, { separator: "|" });

          docMaskResult = {
            id: maskElements[0],
            maskAcl: maskElements[1],
            storageId: maskElements[2],
            colorId: maskElements[3],
            flags: maskElements[4],
            name: docMaskName,
            nameKey: docMaskInfo.MaskNameTK || "",
            filingDefinition: maskElements[7],
            barcode: maskElements[8],
            docAcl: maskElements[10],
            headings: headings,
            headingKeys: headingKeys,
            lifetime: maskElements[12],
            timestamp: maskElements[13],
            defaultFlowName: maskElements[14],
            checkInFlowName: maskElements[15],
            guid: docMaskInfo.MaskGuid || "",
            lines: {}
          };

          j = 0;
          do {
            if (j == 50) {
              j = 60; // skip reserved lines
            }
            lineExists = me.analyzeExportDocLine(docMaskInfo, j, docMaskResult);
            j++;
          } while (lineExists && (j <= DocMaskLineC.MAX_ID_DOCMASK_LINE));

          result.docMasks[docMaskName] = docMaskResult;
        }

        return result;
      },

      /**
       * @private
       * @param {String} exportInfoString
       * @param {Object} params Parameters
       * @param {String} [params.separator=,] Separator
       * @param {String} [params.quoteChar="] Quote char
       * @return {Array}
       */
      splitExportInfoLine: function (exportInfoString, params) {
        var arr = [],
            inValue = false,
            value = "",
            char, i;

        exportInfoString = exportInfoString || "";

        exportInfoString += "";

        params = params || {};
        params.separator = params.separator || ",";
        params.quoteChar = params.quoteChar || "\"";

        for (i = 0; i < exportInfoString.length; i++) {
          char = exportInfoString[i];
          if ((char == params.quoteChar) && ((i == 0) || (i == exportInfoString.length - 1) || (exportInfoString[i - 1] == params.separator) || (exportInfoString[i + 1] == params.separator))) {
            inValue = !inValue;
          } else if ((char == params.separator) && !inValue) {
            arr.push(value);
            value = "";
          } else {
            value += char;
          }

          if (i == exportInfoString.length - 1) {
            arr.push(value);
          }
        }

        return arr;
      },

      /**
       * @private
       * @param {Object} docMaskInfo
       * @param {Number} lineIndex
       * @param {Object} docMaskResult
       * @return {Boolean} lineExists
       */
      analyzeExportDocLine: function (docMaskInfo, lineIndex, docMaskResult) {
        var me = this,
            posLines = [],
            linePositions = [],
            lineInfo, j, posLine, positionString, positions, propCount, startPropIndex,
            lineInfoElements, lineKey, lineFlags, lineObj;

        for (j = 0; j < 100; j++) {
          posLine = docMaskInfo["MaskPosInfo" + j];
          if (!posLine) {
            break;
          }
          posLines.push(posLine);
        }
        positionString = posLines.join("");
        if (positionString) {
          positionString = positionString.substr(1);
          positions = positionString.split(",");

          propCount = 6;
          startPropIndex = (lineIndex * propCount);
          linePositions = positions.slice(startPropIndex, startPropIndex + propCount);
        } else {
          linePositions = [0, 0, 0, 0, 0];
        }

        lineInfo = docMaskInfo["MaskLine" + lineIndex];

        if (!lineInfo) {
          return false;
        }
        lineInfoElements = me.splitExportInfoLine(lineInfo);
        lineKey = lineInfoElements[6];
        lineFlags = parseInt(lineInfoElements[7], 10);

        lineObj = {
          id: lineInfoElements[1],
          rawDataType: parseInt(lineInfoElements[2], 10),
          minLen: lineInfoElements[3],
          maxLen: lineInfoElements[4],
          name: lineInfoElements[5],
          nameKey: docMaskInfo["LineNameTK" + lineIndex] || "",
          key: lineKey,

          onlyKeywordListData: ((lineFlags & 1) == 1),
          prefixAsterix: ((lineFlags & 2) == 2),
          postfixAsterix: ((lineFlags & 4) == 4),
          nextTab: ((lineFlags & 8) == 8),
          hidden: ((lineFlags & 0x10) == 0x10),
          readOnly: ((lineFlags & 0x20) == 0x20),
          important: ((lineFlags & 0x40) == 0x40),
          displayOnCheckIn: ((lineFlags & 0x80) == 0x80),
          translateKeywordList: ((lineFlags & 0x100) == 0x100),
          disableF7Search: ((lineFlags & 0x200) == 0x200),
          inheritToChildren: ((lineFlags & 0x400) == 0x400),
          notTokenized: ((lineFlags & 0x800) == 0x800),
          inheritFromParent: ((lineFlags & 0x1000) == 0x1000),
          excludeFromISearch: ((lineFlags & 0x2000) == 0x2000),
          valueArray: ((lineFlags & 0x4000) == 0x4000),
          required: ((lineFlags & 0x8000) == 0x8000),

          acl: lineInfoElements[8],
          quickInfo: lineInfoElements[9] || "",
          quickInfoKey: docMaskInfo["LineCommentTK" + lineIndex] || "",
          externalData: lineInfoElements[10],
          defaultValue: lineInfoElements[11],
          tabIndex: lineInfoElements[12],
          guid: lineInfo.MaskGuid,
          script: docMaskInfo["serverScriptName" + lineIndex] || "",

          labelCol: linePositions[0],
          labelRow: linePositions[1],
          editCol: linePositions[2],
          editRow: linePositions[3],
          editWidth: linePositions[4],
          tabOrder: linePositions[5]
        };

        docMaskResult.lines[lineKey] = lineObj;

        return true;
      },

      /**
       * Compares the export data
       * @param {Object} exportData Export data
       * @return {Object} Difference
       */
      compareExportData: function (exportData) {
        var me = this,
            diffData = {
              docMasks: [],
              hints: []
            },
            maskName, docMaskData;

        if (!exportData) {
          throw "Export data is empty";
        }

        for (maskName in exportData.docMasks) {
          docMaskData = exportData.docMasks[maskName];
          me.compareDocMask(maskName, docMaskData, diffData);
        }

        return diffData;
      },

      /**
       * @private
       * Compares a document mask
       * @param {String} maskName Mask name
       * @param {Object} docMaskData Document mask data
       * @param {Object} diffData data
       */
      compareDocMask: function (maskName, docMaskData, diffData) {
        var me = this,
            lines, lineKey, maskGuid, docMaskLine, line, docMask, docMaskDiffData;

        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, false);

        maskGuid = docMaskData.guid;

        try {
          docMask = ixConnect.ix().checkoutDocMask(maskGuid + "", DocMaskC.mbAll, LockC.NO);
        } catch (ignore) {
        }

        if (!docMask) {
          try {
            docMask = ixConnect.ix().checkoutDocMask(maskName + "", DocMaskC.mbAll, LockC.NO);
          } catch (ignore) {
          }
        }

        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, true);

        if (!docMask) {
          diffData.hints.push("Can't read document mask '" + maskName + "' " + maskGuid);

          return;
        }

        for (lineKey in docMaskData.lines) {
          line = docMaskData.lines[lineKey];

          docMaskLine = me.getDocMaskLine(docMask, lineKey);

          if (!docMaskLine && (lineKey != "_RESERVED")) {
            lines = lines || [];
            lines.push(line);
          }
        }

        docMaskDiffData = {
          maskName: maskName,
          dstMaskGuid: docMask.guid + "",
          lines: lines
        };
        if (lines) {
          diffData.docMasks.push(docMaskDiffData);
        }
      },

      /**
       * @private
       * Returns the document mask line
       * @param {de.elo.ix.client.DocMask} docMask Document mask
       * @param {String} lineKey Line key
       * @return {de.elo.ix.client.DocMaskLine} Document mask line
       */
      getDocMaskLine: function (docMask, lineKey) {
        var i, docMaskLine;

        for (i = 0; i < docMask.lines.length; i++) {
          docMaskLine = docMask.lines[i];
          if (docMaskLine.key == lineKey) {
            return docMaskLine;
          }
        }
      }
    });

    sol.define("sol.dev.install.InstallHandlerBase", {

      /**
       * @cfg {String} payloadDirPath (required)
       */

      /**
       * @cfg {String} result (required)
       */

      initialize: function (config) {
        var me = this;

        me.$super("sol.Base", "initialize", [config]);

        if (!me.config) {
          throw "Parameter 'config' must be given";
        }

        if (!me.result) {
          throw "Parameter 'result' must be given";
        }
      },

      /**
       * Log
       * @param {Object} message Message
       * @param {String} message.text Message text
       * @param {String} message.data Message data
       */
      log: function (message) {
        var me = this;

        me.prepareMessage(message);
        me.logMessage(message);
      },

      /**
       * Process message
       * @param {Object} message Message
       * @param {String} message.key Message key
       * @param {String} message.data Message data
       * @return {Object}
       */
      prepareMessage: function (message) {
        var me = this,
            args;

        message = message || {};

        if (message.key) {
          message.text = me.getText(message.key);
          delete message.key;
        }
        if (message.data) {
          args = message.data || [];
          args.unshift(message.text);
          message.text = sol.common.StringUtils.format.apply(me, args);
          delete message.data;
        }

        return message;
      },

      addIssue: function (issue) {
        var me = this;

        me.result.issues.items.push(issue);
      },

      processIssues: function () {
        var me = this,
            scriptDirPath, htmlTemplate, textTemplate, textContent, lines;

        if (me.result.issues.items.length > 0) {
          me.result.issues.items.forEach(function (issue) {
            me.prepareMessage(issue.title);
            me.prepareMessage(issue.details);
          });
          me.result.issues.title = me.getText("sol.dev.install.title.issues");
          me.result.issues.continueSetup = me.getText("sol.dev.install.text.continueSetup");

          scriptDirPath = String(new File(me.payloadDirPath).parent);

          textTemplate = sol.common.FileUtils.readFileToString(scriptDirPath + File.separator + "issues_text_tpl.txt");
          textContent = sol.create("sol.common.Template", { source: textTemplate }).apply(me.result.issues);

          lines = sol.common.StringUtils.splitLines(textContent);
          lines.forEach(function (line) {
            me.logMessage({ text: line });
          });

          htmlTemplate = sol.common.FileUtils.readFileToString(scriptDirPath + File.separator + "issues_html_tpl.txt");
          me.result.issues.htmlContent = sol.create("sol.common.Template", { source: htmlTemplate }).apply(me.result.issues);

          me.handleIssues();
        }
      },

      /**
       * @abstract
       * @param {Object} params Parameters
       * @param {String} params.message Message
       */
      logMessage: function (params) {
        throw "Can't call abstract method 'sol.dev.install.InstallHandlerBase.logMessage()'";
      },

      /**
       * @abstract
       * @param {String} key
       */
      getText: function (key) {
        throw "Can't call abstract method 'sol.dev.install.InstallHandlerBase.getText()'";
      },

      /**
       * @abstract
       * @param {String} key
       */
      getTempDir: function (key) {
        throw "Can't call abstract method 'sol.dev.install.InstallHandlerBase.getTempDir()'";
      },

      /**
       * @abstract
       */
      handleIssues: function () {
        throw "Can't call abstract method 'sol.dev.install.InstallHandlerBase.handleIssues()'";
      },

      handleExportInfoDiffs: function () {
        return true;
      }
    });

    sol.define("sol.dev.install.JcHandler", {
      extend: "sol.dev.install.InstallHandlerBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallHandlerBase", "initialize", [config]);
        me.fileLogger = sol.create("sol.dev.install.FileLogger", {
          tempDir: me.getTempDir(),
          config: me.config
        });

        if (workspace.directories.payloadDir) {
          me.payloadDirPath = String(workspace.directories.payloadDir.canonicalPath);
        }
      },

      getTempDir: function () {
        return workspace.directories.tempDir;
      },

      /**
       * @private
       * Writes a log message
       * @param {Object} message Log message
       * @param {String} message.text Messge text
       * @param {Boolean} message.statusBar Write to status bar
       */
      logMessage: function (message) {
        var me = this;

        message = message || {};

        me.fileLogger.log(message);
        if (message.statusBar) {
          workspace.setStatusMessage(message.text);
        }
      },

      handleIssues: function () {
        var me = this,
            htmlDialogContentTempFilePath, htmlDialogContentUrl, dialogResult;

        if (me.result.issues.htmlContent) {
          htmlDialogContentTempFilePath = me.getTempDir().canonicalPath + File.separator + "HtmlDialogContent_" + java.lang.System.nanoTime() + ".html";
          sol.common.FileUtils.writeStringToFile(htmlDialogContentTempFilePath, me.result.issues.htmlContent, { bom: true });
          htmlDialogContentUrl = sol.common.FileUtils.getUrlFromFilePath(htmlDialogContentTempFilePath);
          dialogResult = me.showHtmlDialog({ title: me.getText("sol.dev.install.title.installer"), url: htmlDialogContentUrl });
          me.log({ text: "dialogResult={0}", data: [dialogResult] });
          me.result.canceled = !dialogResult;
          if (!me.result.canceled) {
            globalScope.runningSetup = me.config.install.setupName;
          }
        }
      },

      handleExportInfoDiffs: function () {
        var me = this,
            htmlDialogContentTempFilePath, htmlDialogContentUrl, dialogResult;

        if (me.result.exportInfoDiffs.htmlContent) {
          htmlDialogContentTempFilePath = me.getTempDir().canonicalPath + File.separator + "HtmlDialogContent_" + java.lang.System.nanoTime() + ".html";
          sol.common.FileUtils.writeStringToFile(htmlDialogContentTempFilePath, me.result.exportInfoDiffs.htmlContent, { bom: true });
          htmlDialogContentUrl = sol.common.FileUtils.getUrlFromFilePath(htmlDialogContentTempFilePath);
          dialogResult = me.showHtmlDialog({ title: me.getText("sol.dev.install.title.installer"), url: htmlDialogContentUrl });
          me.log({ text: "dialogResult={0}", data: [dialogResult] });
          return dialogResult;
        }
      },

      /**
       * Shows a HTML dialog
       * @param {Object} params Parameter
       * @param {String} params.title Title
       * @param {String} params.url URL
       * @return {Boolean}
       */
      showHtmlDialog: function (params) {
        var appDialog, result;

        params = params || {};
        params.title = params.title || "";
        params.url = params.url || "";
        appDialog = workspace.createAppDialog(params.title);
        appDialog.loadUrl(params.url);
        result = appDialog.show();
        return result;
      },

      finalize: function () {
        var me = this,
            message, messageDetails, content;

        if (me.config.install.interactive) {
          return;
        }

        if (me.result.canceled) {
          message = me.result.canceledMessage || me.prepareMessage({ key: "sol.dev.install.message.canceled", data: [me.config.install.setupName], statusBar: true });
          me.logMessage(message);
          content = "<h3>" + message.text + "</h3>";
          workspace.showAlertBox(me.getText("sol.dev.install.title.installer"), content);
        } else {
          message = me.prepareMessage({ key: "sol.dev.install.message.finished", data: [me.config.install.setupName], statusBar: true });
          me.log(message);
          messageDetails = me.prepareMessage({ key: "sol.dev.install.message.finishedDetails" });
          me.log(messageDetails);
          content = "<h3>" + message.text + "</h3>" + messageDetails.text;
          workspace.showInfoBox(me.getText("sol.dev.install.title.installer"), content);
          globalScope.runningSetup = "";
        }

        me.fileLogger.finalize();

        try {
          Packages.de.elo.client.ioutil.Installer.message = message.text;
        } catch (ignore) {
          // ignore
        }

        if (me.debug || me.result.canceled) {
          Packages.java.awt.Desktop.getDesktop().open(me.fileLogger.logFile);
        }
      },

      /**
       * Helper function that returns the localized text of a resource file
       * @param {String} key Key of the text.
       * @return {String} Localized text.
       */
      getText: function (key) {
        return String(utils.getText("sol.dev.install", key));
      }
    });

    sol.define("sol.dev.install.AsHandler", {
      extend: "sol.dev.install.InstallHandlerBase",

      initialize: function (config) {
        var me = this,
            scriptDirPath;

        me.$super("sol.dev.install.InstallHandlerBase", "initialize", [config]);
        me.fileLogger = sol.create("sol.dev.install.FileLogger", {
          tempDir: me.getTempDir(),
          config: me.config
        });

        me.payloadDirPath = String(Packages.de.elo.mover.main.helper.ScriptHelper.getPayloadDirPath());
        scriptDirPath = String(new File(me.payloadDirPath).parent);

        me.props = sol.create("sol.dev.install.MultiProperties", { filePath: scriptDirPath + File.separator + "text_sol.dev.install.properties", language: "en" });
      },

      getTempDir: function () {
        var tempDirPath, tempDir;

        tempDirPath = sol.common.FileUtils.getTempDirPath();
        tempDir = new File(tempDirPath);
        return tempDir;
      },

      /**
       * @private
       * Writes a log message
       * @param {Object} message Log message
       * @param {String} message.text Messge text
       * @param {Boolean} message.statusBar Write to status bar
       */
      logMessage: function (message) {
        var me = this;

        message = message || {};

        me.fileLogger.log(message);
        if (message && message.text) {
          log.info(message.text);
        }
      },

      finalize: function () {
        var me = this,
            message;

        message = me.prepareMessage({ key: "sol.dev.install.message.finished", data: [me.config.install.setupName] });
        me.log(message);

        me.fileLogger.finalize();

        if (typeof installResult != "undefined") {
          installResult.httpStatus = "200";
          installResult.contentType = "application/json";
          installResult.httpContent = JSON.stringify(me.result);
        }
      },

      handleIssues: function () {
        var me = this;

        if (me.result.fatal) {
          me.result.canceled = true;
        }
      },

      getText: function (key) {
        var me = this;

        return me.props.getText(key);
      }
    });

    sol.define("sol.dev.install.IxHandler", {
      extend: "sol.dev.install.InstallHandlerBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallHandlerBase", "initialize", [config]);
        me.stringLogger = sol.create("sol.dev.install.StringLogger", { config: me.config });
        sol.common.TranslateTerms.require("sol.dev.install");
      },

      /**
       * @private
       * Writes a log message
       * @param {Object} message Log message
       * @param {String} message.text Messge text
       */
      logMessage: function (message) {
        var me = this;

        message = message || {};

        me.stringLogger.log(message);
      },

      finalize: function () {
        var me = this,
            message;

        message = me.prepareMessage({ key: "sol.dev.install.message.finished", data: [me.config.install.setupName] });
        me.logMessage(message);

        me.stringLogger.finalize();
      },

      handleIssues: function () {
        var me = this;

        if (me.result.fatal) {
          me.result.canceled = true;
        }
      },

      getText: function (key) {
        return sol.common.TranslateTerms.translate(key);
      }
    });

    sol.define("sol.dev.install.ShHandler", {
      extend: "sol.dev.install.InstallHandlerBase",

      initialize: function (config) {
        var me = this,
            scriptDirPath;

        me.$super("sol.dev.install.InstallHandlerBase", "initialize", [config]);
        me.fileLogger = sol.create("sol.dev.install.FileLogger", {
          tempDir: me.getTempDir(),
          config: me.config
        });

        if (typeof ELOINSTDIR != "undefined") {
          me.payloadDirPath = ELOINSTDIR + File.separator + "install.data";
          scriptDirPath = ELOINSTDIR;
        } else {
          me.payloadDirPath = __DIR__ + "install.data";
          scriptDirPath = String(new File(me.payloadDirPath).parent);
        }

        me.props = sol.create("sol.dev.install.MultiProperties", { filePath: scriptDirPath + File.separator + "text_sol.dev.install.properties", language: "en" });
      },

      getTempDir: function () {
        var tempDirPath, tempDir;

        tempDirPath = sol.common.FileUtils.getTempDirPath();
        tempDir = new File(tempDirPath);
        return tempDir;
      },

      /**
       * @private
       * Writes a log message
       * @param {Object} message Log message
       * @param {String} message.text Messge text
       * @param {Boolean} message.statusBar Write to status bar
       */
      logMessage: function (message) {
        var me = this;

        message = message || {};

        me.fileLogger.log(message);
        if (message && message.text) {
          log.info(message.text);
        }
      },

      finalize: function () {
        var me = this,
            message;

        message = me.prepareMessage({ key: "sol.dev.install.message.finished", data: [me.config.install.setupName] });
        me.log(message);

        me.fileLogger.finalize();

        if (typeof installResult != "undefined") {
          installResult.httpStatus = "200";
          installResult.contentType = "application/json";
          installResult.httpContent = JSON.stringify(me.result);
        }
      },

      handleIssues: function () {
        var me = this;

        if (me.result.fatal) {
          me.result.canceled = true;
        }
      },

      getText: function (key) {
        var me = this;

        return me.props.getText(key);
      }
    });

    sol.define("sol.dev.install.TestHandler", {
      extend: "sol.dev.install.JcHandler",

      initialize: function (config) {
        var me = this,
            scriptDirPath, scriptDir, payloadDir;

        me.$super("sol.dev.install.InstallHandlerBase", "initialize", [config]);

        scriptDirPath = me.config.install.scriptDirPath || "c:/temp/BsInstall";
        me.payloadDirPath = scriptDirPath + File.separator + "install.data";

        scriptDir = new File(scriptDirPath);
        payloadDir = new File(me.payloadDirPath);

        sol.common.FileUtils.makeDirectories(payloadDir);
        sol.common.FileUtils.deleteFiles({ dirPath: scriptDirPath });

        me.fileLogger = sol.create("sol.dev.install.FileLogger", {
          tempDir: scriptDir,
          config: me.config
        });

        sol.common.FileUtils.downloadDocuments("ARCPATH[(E10E1000-E100-E100-E100-E10E10E10E00)]:/Business Solutions/development/Package Installer", scriptDirPath);

        me.props = sol.create("sol.dev.install.MultiProperties", { filePath: scriptDirPath + File.separator + "text_sol.dev.install.properties", language: "en" });
      },

      /**
       * @private
       * Writes a log message
       * @param {Object} message Log message
       * @param {String} message.text Messge text
       * @param {Boolean} message.statusBar Write to status bar
       */
      logMessage: function (message) {
        var me = this;

        message = message || {};

        me.fileLogger.log(message);
        if (message && message.text) {
          log.info(message.text);
        }
      },

      finalize: function () {
        var me = this,
            message;

        message = me.prepareMessage({ key: "sol.dev.install.message.finished", data: [me.config.install.setupName] });
        me.log(message);

        me.fileLogger.finalize();
      },

      getText: function (key) {
        var me = this;

        return me.props.getText(key);
      }
    });

    /**
     * Logger base class
     */
    sol.define("sol.dev.install.LoggerBase", {

      initialize: function (params) {
        var me = this;

        me.$super("sol.Base", "initialize", [params]);

        if (!me.config) {
          throw "Setup configuration is empty";
        }

        me.logFileName = "log_install_" + me.config.install.packageName + "_" + me.getCurrentTimeStamp("yyyyMMddHHmmss");
      },

      /**
       * @abstract
       */
      log: function () {
        throw "Can't call abstract method 'sol.dev.install.LoggerBase.log()'";
      },

      /**
       * @private
       * Returns a time stamp of the current time
       * @param {String} format Format of the timestamp.
       * @return {String}
       */
      getCurrentTimeStamp: function (format) {
        return String(new java.text.SimpleDateFormat(format).format(new java.util.Date()));
      },

      /**
       * @private
       * Determinates the log folder ID
       * @return {String} Log folder ID
       */
      getLogFolderId: function () {
        var me = this,
            logFolderId;

        if (me.config.install.packageFolderId) {
          me.logRepoFolderPath = "ARCPATH[" + me.config.install.packageFolderId + "]:/.eloinst";
        } else {
          me.logRepoFolderPath = sol.common.RepoUtils.resolveSpecialFolder("{{packageFolderPath}}/.eloinst", me.config.base.repoPathParamObj);
        }
        logFolderId = sol.common.RepoUtils.getObjId(me.logRepoFolderPath);
        return logFolderId;
      },

      /**
       * @abstract
       */
      finalize: function () {
        throw "Can't call abstract method 'sol.dev.install.LoggerBase.execute()'";
      }
    });

    /**
     * File logger
     */
    sol.define("sol.dev.install.FileLogger", {
      extend: "sol.dev.install.LoggerBase",

      /**
       * @cfg {java.io.File} tempDir
       * Temp directory
       */

      initialize: function (params) {
        var me = this;

        me.$super("sol.dev.install.LoggerBase", "initialize", [params]);

        if (!me.tempDir) {
          throw "Temp directory is empty";
        }

        me.logFile = new File(params.tempDir.canonicalPath + File.separator + me.logFileName + ".txt");

        me.writer = new PrintWriter(new FileWriter(me.logFile), true);
      },

      /**
       * Writes a message into the log file
       * @param {Object} message
       */
      log: function (message) {
        var me = this,
            line;

        message = message || {};
        line = me.getCurrentTimeStamp("yyyy-MM-dd HH:mm:ss.SSS") + ": " + message.text;
        me.writer.println(line);
      },

      /**
       * Finalize the log file
       */
      finalize: function () {
        var me = this,
            logFolderId;

        me.writer.close();

        logFolderId = me.getLogFolderId();
        if (logFolderId) {
          sol.common.RepoUtils.saveToRepo({ parentId: logFolderId, name: me.logFileName, file: me.logFile });
        }
      }
    });

    /**
     * String logger
     */
    sol.define("sol.dev.install.StringLogger", {
      extend: "sol.dev.install.LoggerBase",

      initialize: function (params) {
        var me = this;

        me.$super("sol.dev.install.LoggerBase", "initialize", [params]);

        me.lines = [];
      },

      /**
       * Writes a message into the log file
       * @param {Object} message
       */
      log: function (message) {
        var me = this,
            line;

        message = message || {};
        line = me.getCurrentTimeStamp("yyyy-MM-dd HH:mm:ss.SSS") + ": " + message.text;
        me.lines.push(line);
      },

      /**
       * Finalize the log file
       */
      finalize: function () {
        var me = this,
            logFolderId, content;

        content = me.lines.join("\r\n");
        log.debug(content);

        logFolderId = me.getLogFolderId();
        if (logFolderId) {
          sol.common.RepoUtils.saveToRepo({
            parentId: logFolderId,
            name: me.logFileName,
            extension: "txt",
            contentString: content,
            withoutBom: true
          });
        }
      }
    });

    var CURR_INST_FUNC;

    sol.define("sol.dev.install.InstallFunctionBase", {

      /**
       * @cfg {Object} install (required)
       * Installation configuration
       */

      /**
       * @cfg {Object} result
       * Output
       */

      /**
       * @cfg {String} srcDirPath
       * Path of the source directory. If the directory doesn't exist, then the step will be skipped.
       */

      /**
       * @cfg {Array} handlers
       * Installation handlers, e.g. ´sol.dev.install.JcHandler´, ´sol.dev.install.AsHandler´, ´sol.dev.install.IxHandler´ or ´sol.dev.install.ShHandler´
       */

      /**
       * @cfg {Object} config (required)
       * Configuration
       */

      /**
       * @cfg {Object} config.base
       * Base configuration
       */

      /**
       * @cfg {Object} config.base.repoPathParamObj
       * Object that contains data to resolve repository paths
       */

      /**
       * @cfg {Object} config.install Installation configuration
       */

      /**
       * @cfg {Object} config.handler Install handler
       */

      pilcrow: "\u00b6",

      initialize: function (params) {
        var me = this;

        if (me.$className === "sol.dev.install.InstallFunctionBase") {
          throw "Can't create instance of base class 'sol.dev.install.InstallFunctionBase'";
        }

        if (!params.config) {
          throw "Configuration must be given";
        }

        if (!params.config.install) {
          throw "Parameter 'config.install' must be given";
        }

        if (!params.config.base) {
          throw "Parameter 'config.base' must be given";
        }

        if (!params.handler) {
          throw "Parameter 'handler' must be given";
        }

        if (!params.result) {
          throw "Parameter 'result' must be given";
        }

        me.$super("sol.Base", "initialize", [params]);
      },

      /**
       * Checks wether the function should run
       * This function can be overwritten.
       * @return {Boolean}
       */
      check: function () {
        return true;
      },

      /**
       * Processes the installation function
       */
      process: function () {
        var me = this;

        if (!me.check()) {
          return;
        }

        if (me.srcDirPath) {
          if (sol.common.FileUtils.exists(me.srcDirPath)) {
            me.srcDir = new File(me.srcDirPath);
          } else {
            return;
          }
        }

        if (me.handlers && (me.handlers.indexOf(me.handler.$className) < 0)) {
          me.handler.log({ text: "Installation function '" + me.$className + "' skipped." });
          return;
        }
        me.execute();
      },

      /**
       * @abstract
       */
      execute: function () {
        throw "Can't call abstract method 'sol.dev.install.InstallFunctionBase.execute()'";
      }
    });

    sol.define("sol.dev.install.mixin.InstallUtils", {
      mixin: true,

      /**
       * Sends a HTTP requests
       * @param {String} description
       * @param {String} url
       * @param {Boolean} params Parameters
       * @param {String} [params.method=get] Method
       * @param {Number} [params.readTimeout=10000] Read timeout
       */
      sendHttpRequest: function (description, url, params) {
        var me = this,
            responseObj, logStr,
            httpRequest = {};

        params = params || {};
        params.method = params.method || "get";
        params.readTimeout = params.readTimeout || 10000;

        httpRequest.url = sol.common.HttpUtils.resolveUrl(url, params);
        me.handler.log({ text: description + ": " + httpRequest.url, statusBar: true });
        httpRequest.resolve = false;
        httpRequest.connectTimeout = 3000;
        httpRequest.readTimeout = params.readTimeout;
        httpRequest.trustAllHosts = true;
        httpRequest.trustAllCerts = true;
        if (params.method == "post") {
          httpRequest.method = "post";
          httpRequest.contentType = "application/json;charset=UTF-8";
          httpRequest.addCookieTicket = true;
        }
        responseObj = sol.common.HttpUtils.sendRequest(httpRequest);
        if (responseObj.errorMessage) {
          logStr = "errorMessage=" + responseObj.errorMessage;
        } else {
          logStr = "responseCode=" + responseObj.responseCode + ", ok=" + responseObj.responseOk;
        }
        me.handler.log({ text: logStr });
      }
    });

    sol.define("sol.dev.install.functions.CheckRunningSetup", {
      extend: "sol.dev.install.InstallFunctionBase",

      handlers: ["sol.dev.install.JcHandler"],

      /**
       * Checks wether another installation is running
       */
      execute: function () {
        var me = this;

        if (globalScope.runningSetup) {
          me.result.canceled = true;
          me.result.canceledMessage = me.handler.prepareMessage({ key: "sol.dev.install.message.setupAlreadyRunning", data: [me.config.install.setupName], statusBar: true });
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckJcRequirements", {
      extend: "sol.dev.install.InstallFunctionBase",

      handlers: ["sol.dev.install.JcHandler"],

      /**
       * Checks the JC requirements of the installation
       */
      execute: function () {
        var me = this,
            requiredJavaClientVersion;

        requiredJavaClientVersion = me.config.install.requiredJavaClientVersion || "9.03.006";
        me.handler.log({ text: "ELO Java Client version: {0}", data: [String(workspace.clientVersion)] });
        if (!sol.common.RepoUtils.checkVersion(workspace.clientVersion, requiredJavaClientVersion)) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.higherJcVersionRequired", data: [requiredJavaClientVersion] }
          });
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckIxRequirements", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Checks the IX requirements of the installation
       */
      execute: function () {
        var me = this,
            requiredIxVersion, currentIxVersion;

        try {
          currentIxVersion = ixConnect.version;
        } catch (ex) {
          me.handler.addIssue({
            messages: [{ key: "sol.dev.install.message.cannotDetectIxVersion" }]
          });
          return;
        }

        requiredIxVersion = me.config.install.requiredIxVersion || "9.00.050";
        me.handler.log({ text: "IX version: {0}", data: [String(ixConnect.version)] });
        if (!sol.common.RepoUtils.checkVersion(currentIxVersion, requiredIxVersion)) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.higherIxVersionRequired", data: [requiredIxVersion] }
          });
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckPermissions", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Checks the rights
       */
      execute: function () {
        var me = this,
            permissions, missingPermissions;

        permissions = ["FLAG_ADMIN", "FLAG_IMPORT"];

        missingPermissions = sol.common.UserUtils.checkCurrentPermissions(permissions);

        if (missingPermissions && (missingPermissions.length > 0)) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.missingPermissions" },
            list: missingPermissions
          });
          me.result.fatal = true;
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckPackageAlreadyInstalled", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Checks wether the package is already installed
       */
      execute: function () {
        var me = this,
            path, exists;

        path = sol.common.RepoUtils.resolveSpecialFolder("{{packageFolderPath}}", me.config.base.repoPathParamObj);

        exists = sol.common.RepoUtils.exists(path);
        if (exists) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.packageAlreadyInstalled", data: [me.config.install.setupName] },
            details: { key: "sol.dev.install.message.packageAlreadyInstalledDetails" }
          });
          me.result.undeploy = true;
        }
      }
    });

    sol.define("sol.dev.install.functions.TransportWarning", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Transport warning
       */
      execute: function () {
        var me = this;

        me.handler.addIssue({
          title: { key: "sol.dev.install.message.transportPackage" },
          details: { key: "sol.dev.install.message.transportPackageCouldDamageRepo" }
        });
      }
    });

    sol.define("sol.dev.install.functions.CheckDocMasksMustNotExist", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.docMasksMustNotExist;
      },

      execute: function () {
        var me = this,
            existingMaskNames = [],
            i, maskName, exists, maskNamesExists;

        for (i = 0; i < me.config.install.docMasksMustNotExist.length; i++) {
          maskName = me.config.install.docMasksMustNotExist[i];

          exists = sol.common.SordUtils.docMaskExists(maskName);
          if (exists) {
            existingMaskNames.push(maskName);
            maskNamesExists = true;
          }
        }

        if (maskNamesExists) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.masksAlreadyExist" },
            list: existingMaskNames
          });
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckRepoPathsMustNotExist", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.repoPathsMustNotExist;
      },

      execute: function () {
        var me = this,
            existingRepoPaths = [],
            repoPathsExist = false,
            i, repoPath, exists;

        for (i = 0; i < me.config.install.repoPathsMustNotExist.length; i++) {
          repoPath = me.config.install.repoPathsMustNotExist[i];
          repoPath = sol.common.RepoUtils.resolveSpecialFolder(repoPath, me.config.base.repoPathParamObj);
          repoPath = sol.common.RepoUtils.normalizePath(repoPath, true);

          exists = sol.common.RepoUtils.exists(repoPath);
          if (exists) {
            existingRepoPaths.push(repoPath);
            repoPathsExist = true;
          }
        }

        if (repoPathsExist) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.repoPathsAlreadyExist", list: existingRepoPaths }
          });
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckWfIsRunning", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return (me.config.install.checkWfIsRunning);
      },

      /**
       * Checks wether the ELOwf is running
       */
      execute: function () {
        var me = this,
            resultObj, url, errorMessage;

        resultObj = sol.common.WfUtils.checkWfIsRunning();
        if (!resultObj.isRunning) {
          url = resultObj.url;
          errorMessage = resultObj.errorMessage || "";
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.wfNotRunning" },
            details: { key: "sol.dev.install.message.wfUrl", data: [url, errorMessage] }
          });
        }
      }
    });

    sol.define("sol.dev.install.functions.CheckAsLibs", {
      extend: "sol.dev.install.InstallFunctionBase",

      libRepoPaths: [
        "{{{asBaseFolderPath}}}/JavaScript/ix: IndexServer Functions",
        "{{{asBaseFolderPath}}}/JavaScript/fu: File Utils"
      ],

      /**
       * Checks wether the ELOas libraries are incorrect or outdated
       */
      execute: function () {
        var me = this,
            incorrectLibs = [],
            i, libRepoPath, displayLibRepoPath, incorrect;

        for (i = 0; i < me.libRepoPaths.length; i++) {
          libRepoPath = sol.common.RepoUtils.resolveSpecialFolder(me.libRepoPaths[i], me.config.base.repoPathParamObj);
          incorrect = me.isLibIncorrect(libRepoPath);
          if (incorrect) {
            displayLibRepoPath = sol.common.StringUtils.replaceAll(libRepoPath, me.pilcrow, "/");
            incorrectLibs.push(displayLibRepoPath);
          }
        }

        if (incorrectLibs.length > 0) {
          me.handler.addIssue({
            title: { key: "sol.dev.install.message.asLibsIncorrect" },
            list: incorrectLibs
          });
        }
      },

      isLibIncorrect: function (repoPath) {
        var objId, sord, isFolder, content, remainder,
            lastCurlyBracketPos, nextChar;

        repoPath = sol.common.RepoUtils.resolveSpecialFolder(repoPath);
        objId = sol.common.RepoUtils.getObjId(repoPath);
        if (objId) {
          sord = sol.common.RepoUtils.getSord(objId, { sordZ: SordC.mbLean });
          isFolder = sol.common.SordUtils.isFolder(sord);
          if (isFolder) {
            return true;
          }
          try {
            content = sol.common.RepoUtils.downloadToString(objId);
          } catch (ex) {
            return true;
          }
          lastCurlyBracketPos = content.lastIndexOf("}");
          if (lastCurlyBracketPos > -1) {
            if (content.length > (lastCurlyBracketPos + 1)) {
              remainder = content.substr(lastCurlyBracketPos + 1).replace(/\s/g, "");
              nextChar = remainder.substr(0, 1);
              if (nextChar == ";") {
                return false;
              }
            }
          }
        }
        return true;
      }
    });

    sol.define("sol.dev.install.functions.CheckPackageDependencyInstalled", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Checks wether the package is already installed
       */
      execute: function () {
        var me = this,
            missingPackages = [], notRequiredVersions = [], key, sord, exists,
            i, delim = sol.common.RepoUtils.getPathSeparator(sol.common.RepoUtils.specialFolders.bsFolder[1]), dependency, path, version;

        if (me.config.install.dependencies && me.config.install.dependencies.length > 0) {
          me.handler.log({ text: "Dependency found, amount: " + me.config.install.dependencies.length });
          for (i = 0; i < me.config.install.dependencies.length; i++) {
            dependency = me.config.install.dependencies[i];
            me.handler.log({ text: "Checking dependency " + i + ": " + dependency.packageName });
            if (dependency.checkBase) {
              path = sol.common.RepoUtils.resolveSpecialFolder(sol.common.RepoUtils.specialFolders.bsFolder[1] + delim + dependency.packageName);
              exists = sol.common.RepoUtils.exists(path);
              if (!exists) {
                missingPackages.push(dependency.packageName);
              } else if (dependency.minReqVersionBase) {
                sord = sol.common.RepoUtils.getSord(path, { sordZ: SordC.mbLean });
                version = sol.common.SordUtils.getObjKeyValue(sord, "BS_VERSION_NO");
                if (!sol.common.RepoUtils.checkVersion(version, dependency.minReqVersionBase)) {
                  notRequiredVersions.push(dependency.packageName + " " + version + " &lt; " + dependency.minReqVersionBase);
                }
              }
            }
            if (dependency.checkCustom) {
              path = sol.common.RepoUtils.resolveSpecialFolder(sol.common.RepoUtils.specialFolders.bsFolder[1] + " Custom" + delim + dependency.packageName);
              exists = sol.common.RepoUtils.exists(path);
              if (!exists) {
                missingPackages.push("custom_" + dependency.packageName);
              } else if (dependency.minReqVersionCustom) {
                sord = sol.common.RepoUtils.getSord(path, { sordZ: SordC.mbLean });
                version = sol.common.SordUtils.getObjKeyValue(sord, "BS_VERSION_NO");
                if (!sol.common.RepoUtils.checkVersion(version, dependency.minReqVersionCustom)) {
                  notRequiredVersions.push("custom_" + dependency.packageName + " " + version + " &lt; " + dependency.minReqVersionCustom);
                }
              }
            }
          }
          if (missingPackages.length > 0 && notRequiredVersions.length > 0) {
            me.result.canceledMessage = me.handler.prepareMessage({ key: "sol.dev.install.message.bothNotSucceededDependencyDetails",
              data: [missingPackages.join(", "), notRequiredVersions.join(", ")] });
            me.result.canceled = true;
            return;
          }
          if (missingPackages.length > 0) {
            key = "sol.dev.install.message.missingDependencyDetailsSingular";
            if (missingPackages.length > 1) {
              key = "sol.dev.install.message.missingDependencyDetailsMultiple";
            }
            me .result.canceledMessage = me.handler.prepareMessage({ key: key, data: [missingPackages.join(", ")] });
            me.result.canceled = true;
            return;
          }
          if (notRequiredVersions.length > 0) {
            key = "sol.dev.install.message.notRequiredVersionDependencyDetailsSingular";
            if (notRequiredVersions.length > 1) {
              key = "sol.dev.install.message.notRequiredVersionDependencyDetailsMultiple";
            }
            me.result.canceledMessage = me.handler.prepareMessage({ key: key,
              data: [notRequiredVersions.join(", ")] });
            me.result.canceled = true;
            return;
          }
        }
        me.handler.log({ text: "No dependency found" });
      }
    });

    sol.define("sol.dev.install.functions.ProcessIssues", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Process issues
       */
      execute: function () {
        var me = this;

        me.handler.processIssues();
      }
    });

    sol.define("sol.dev.install.functions.CreateUsers", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.users;
      },

      /**
       * Creates new user
       */
      execute: function () {
        var me = this;

        me.handler.log({ text: "Create users...", statusBar: true });
        me.config.install.users.forEach(function (user) {
          var userId;

          if (user.type) {
            user.type = user.type.toLowerCase();
          } else {
            user.type = "user";
          }
          userId = sol.common.UserUtils.createUser(user.name, user);
          if (userId != undefined) {
            me.handler.log({ text: "User/group '" + user.name + "' (ID " + userId + ") created.", statusBar: true });
          } else {
            me.handler.log({ text: "Can't create user/group '" + user.name + "'." });
          }
        });
      }
    });

    sol.define("sol.dev.install.functions.CheckDocMasks", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Check document masks
       */
      execute: function () {
        var me = this,
            exportInfo, exportData, exportDataString, diffData, diffDataString;

        me.config.base.importZipFilePath = me.config.base.importZipFilePath || me.handler.payloadDirPath + File.separator + "RepoData.zip";

        if (me.config.install.expInfoPath) {
          exportInfo = sol.common.FileUtils.readFileToString(me.config.install.expInfoPath, { encoding: "UTF-16LE" });
        } else {
          exportInfo = sol.common.ZipUtils.readFileInZipToString(me.config.base.importZipFilePath, "/ExpInfo.ini", { encoding: "UTF-16LE" });
        }
        exportData = sol.dev.install.RepoDataUtils.exportInfoToObject(exportInfo);
        me.handler.log({ text: "Check document masks...", statusBar: true });
        exportDataString = JSON.stringify(exportData);

        me.handler.log({ text: "Export info: {0}", data: [exportDataString] });

        diffData = sol.dev.install.RepoDataUtils.compareExportData(exportData);

        me.result.exportInfoDiffs = diffData;
        diffDataString = JSON.stringify(diffData);

        me.handler.log({ text: "Differences to the export info file: {0}", data: [diffDataString] });
      }
    });

    sol.define("sol.dev.install.functions.CreateMasks", {
      extend: "sol.dev.install.InstallFunctionBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
        me.prepareCreateMasks();
      },

      prepareCreateMasks: function () {
        var me = this;

        me.masksDirPath = me.handler.payloadDirPath + File.separator + "Masks";
        me.masksDir = new File(me.masksDirPath);

        me.masksFiles = me.masksDir.listFiles() || [];
      },

      check: function () {
        var me = this;

        try {
          java.lang.Class.forName("com.google.gson.GsonBuilder");
        } catch (ex) {
          me.handler.log({ text: "Can't load GSON library. Skip 'CreateMasks'." });
          return false;
        }

        return ((me.config.install.createMasks || me.config.install.transport) && (me.masksFiles && (me.masksFiles.length > 0)));
      },

      /**
       * Create masks
       */
      execute: function () {
        var me = this,
            createdMasksJson = [],
            i, maskFile, origMaskJson, maskJson, maskObj, docMask, maskId, exceptionString,
            childMaskName, childMaskNames, childMaskIds, j, childMaskId;

        me.handler.log({ text: "Create masks...", statusBar: true });

        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, false);

        try {

          for (i = 0; i < me.masksFiles.length; i++) {
            maskFile = me.masksFiles[i];
            origMaskJson = sol.common.FileUtils.readFileToString(maskFile.canonicalPath);

            maskObj = JSON.parse(origMaskJson);
            maskObj.maskIdsForChildEntries = [];

            maskJson = JSON.stringify(maskObj);

            docMask = sol.common.JsonUtils.deserialize(maskJson, "de.elo.ix.client.DocMask");

            maskId = me.getMaskId(docMask.name);
            docMask.id = (typeof maskId == "undefined") ? -1 : maskId;

            if ((docMask.id > -1) && !me.config.install.transport) {
              me.handler.log({ text: "Mask '{0}' (ID {1}) already exists.", data: [docMask.name + "", docMask.id + ""] });
              continue;
            }

            me.handler.log({ text: "Create mask '{0}': {1}", data: [docMask.name + "", maskJson] });

            createdMasksJson.push(origMaskJson);

            ixConnect.ix().checkinDocMask(docMask, DocMaskC.mbAll, LockC.NO);
          }

          if (sol.common.RepoUtils.checkVersion(ixConnect.version, "11.00.000")) {

            for (i = 0; i < createdMasksJson.length; i++) {
              maskJson = createdMasksJson[i];
              maskObj = JSON.parse(maskJson);
              if (maskObj.maskIdsForChildEntries) {
                childMaskNames = [];
                childMaskIds = [];
                for (j = 0; j < maskObj.maskIdsForChildEntries.length; j++) {
                  childMaskName = maskObj.maskIdsForChildEntries[j];
                  childMaskId = me.getMaskId(childMaskName);
                  if (childMaskId > -1) {
                    childMaskNames.push(childMaskName);
                    childMaskIds.push(new java.lang.Integer(childMaskId));
                  }
                }

                if (childMaskNames.length > 0) {
                  docMask = ixConnect.ix().checkoutDocMask(maskObj.name + "", DocMaskC.mbAll, LockC.NO);
                  docMask.maskIdsForChildEntries = childMaskIds;

                  me.handler.log({ text: "Set child masks: mask=" + docMask.name + ", childMasks=" + childMaskNames });

                  ixConnect.ix().checkinDocMask(docMask, DocMaskC.mbAll, LockC.NO);
                }
              }
            }
          }
        } catch (ex) {
          exceptionString = sol.common.ExceptionUtils.parseException(ex);
          me.handler.log({ text: "Can't create mask: name=" + docMask.name + ", exception=" + exceptionString + "json=" + maskJson });
        } finally {
          sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, true);
        }
      },

      getMaskId: function (maskName) {
        var mask;

        try {
          mask = ixConnect.ix().checkoutDocMask(maskName + "", DocMaskC.mbAll, LockC.NO);
          return mask.id + "";
        } catch (ignore) {
        }
      }
    });

    sol.define("sol.dev.install.functions.AdjustDocMasks", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return (me.config.install.adjustDocMasks !== false);
      },

      /**
       * Adjust document masks
       */
      execute: function () {
        var me = this,
            scriptDirPath, textTemplate, textContent, htmlTemplate, lines, applyDiff, i, j, docMaskData, lineData;

        me.handler.log({ text: "Adjust document masks...", statusBar: true });

        if (me.result.exportInfoDiffs && (me.result.exportInfoDiffs.docMasks.length > 0)) {

          me.result.exportInfoDiffs.docMaskDiffs = me.handler.getText("sol.dev.install.title.docMaskDiffs");
          me.result.exportInfoDiffs.adjustDocMasks = me.handler.getText("sol.dev.install.text.adjustDocMasks");

          for (i = 0; i < me.result.exportInfoDiffs.docMasks.length; i++) {
            docMaskData = me.result.exportInfoDiffs.docMasks[i];
            docMaskData.keyText = me.handler.getText("sol.dev.install.text.docMask").replace("{0}", docMaskData.maskName);
            for (j = 0; j < docMaskData.lines.length; j++) {
              lineData = docMaskData.lines[j];
              lineData.keyText = me.handler.getText("sol.dev.install.text.field").replace("{0}", lineData.key);
            }
          }

          scriptDirPath = String(new File(me.handler.payloadDirPath).parent);

          textTemplate = sol.common.FileUtils.readFileToString(scriptDirPath + File.separator + "exportInfoDiffs_text_tpl.txt");
          textContent = sol.create("sol.common.Template", { source: textTemplate }).apply(me.result.exportInfoDiffs);

          lines = sol.common.StringUtils.splitLines(textContent);
          lines.forEach(function (line) {
            me.handler.logMessage({ text: line });
          });

          htmlTemplate = sol.common.FileUtils.readFileToString(scriptDirPath + File.separator + "exportInfoDiffs_html_tpl.txt");
          me.result.exportInfoDiffs.htmlContent = sol.create("sol.common.Template", { source: htmlTemplate }).apply(me.result.exportInfoDiffs);

          applyDiff = me.handler.handleExportInfoDiffs();
          if (applyDiff) {
            me.applyExportInfoDiffs(me.result.exportInfoDiffs);
          }
        } else {
          me.handler.log({ text: "No document masks to adjust" });
        }
      },

      /**
       * Applies the export info differences
       * @param {Object} diffData Export info difference data
       */
      applyExportInfoDiffs: function (diffData) {
        var me = this,
            i, docMaskDiffData;

        if (!diffData) {
          throw "Export info difference data is emtpy";
        }

        for (i = 0; i < diffData.docMasks.length; i++) {
          docMaskDiffData = diffData.docMasks[i];
          me.updateDocMask(docMaskDiffData);
        }
      },

      /**
       * Updates a document mask
       * @param {Object} docMaskDiffData Document mask difference data
       */
      updateDocMask: function (docMaskDiffData) {
        var me = this,
            docMask, i, lines, docMaskLine, docMaskLineData, docMaskLineDataString, nextRow;

        me.handler.log({ text: "Update document mask '{0}'...", data: [docMaskDiffData.maskName], statusBar: true });

        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, false);
        docMask = ixConnect.ix().checkoutDocMask(docMaskDiffData.dstMaskGuid + "", DocMaskC.mbAll, LockC.NO);

        lines = Array.prototype.slice.call(docMask.lines);

        for (i = 0; i < docMaskDiffData.lines.length; i++) {
          docMaskLineData = docMaskDiffData.lines[i];
          docMaskLineDataString = JSON.stringify(docMaskLineData);

          nextRow = me.getNextRow(docMask, docMaskLineData.tabIndex);

          me.handler.log({ text: "Create document mask line: docMask={0}, lineKey={1}, tabIndex={2}, nextRow={3}, data={4}", data: [docMaskDiffData.maskName, docMaskLineData.key, docMaskLineData.tabIndex, nextRow, docMaskLineDataString] });

          docMaskLine = new DocMaskLine();
          docMaskLine.id = -1;

          docMaskLine.type = DocMaskLineC._TYPE_TYPE_ID + docMaskLineData.rawDataType;
          docMaskLine.min = docMaskLineData.minLen;
          docMaskLine.max = docMaskLineData.maxLen;
          docMaskLine.name = docMaskLineData.name;
          docMaskLine.nameTranslationKey = docMaskLineData.nameKey;
          docMaskLine.key = docMaskLineData.key;

          docMaskLine.onlyBuzzwords = docMaskLineData.onlyKeywordListData;
          docMaskLine.prefixAsterix = docMaskLineData.prefixAsterix;
          docMaskLine.postfixAsterix = docMaskLineData.postfixAsterix;
          docMaskLine.nextTab = docMaskLineData.nextTab;
          docMaskLine.hidden = docMaskLineData.hidden;
          docMaskLine.readOnly = docMaskLineData.readOnly;
          docMaskLine.important = docMaskLineData.important;
          docMaskLine.version = docMaskLineData.displayOnCheckIn;
          docMaskLine.translate = docMaskLineData.translateKeywordList;
          docMaskLine.disableWordWheel = docMaskLineData.disableF7Search;
          docMaskLine.inherit = docMaskLineData.inheritToChildren;
          docMaskLine.notTokenized = docMaskLineData.notTokenized;
          docMaskLine.inheritFromParent = docMaskLineData.inheritFromParent;

          docMaskLine.acl = docMaskLineData.acl;
          docMaskLine.comment = docMaskLineData.quickInfo;
          docMaskLine.commentTranslationKey = docMaskLineData.quickInfoKey;
          docMaskLine.externalData = docMaskLineData.externalData;
          docMaskLine.defaultValue = docMaskLineData.defaultValue;
          docMaskLine.tabIndex = docMaskLineData.tabIndex;
          docMaskLine.serverScriptName = docMaskLineData.script;

          if (sol.common.RepoUtils.checkVersion(ixConnect.clientVersion, "9.00.039.001")) {
            docMaskLine.valueArray = docMaskLineData.valueArray;
          }

          if (sol.common.RepoUtils.checkVersion(ixConnect.clientVersion, "10.00.020.010")) {
            docMaskLine.required = docMaskLineData.required;
          }

          if (sol.common.RepoUtils.checkVersion(ixConnect.clientVersion, "10.00.020.023")) {
            docMaskLine.excludeFromISearch = docMaskLineData.excludeFromISearch;
          }

          docMaskLine.editRow = nextRow;
          docMaskLine.labelRow = nextRow;

          lines = me.storeDocMaskLine(lines, docMaskLine);
        }

        docMask.lines = lines;

        me.handler.log({ text: "Write document mask '{0}'", data: [docMaskDiffData.maskName] });

        ixConnect.ix().checkinDocMask(docMask, DocMaskC.mbAll, LockC.NO);
        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, true);
      },

      /**
       * Returns the next free row of the tab
       * @param {de.elo.ix.client.DocMask} docMask Document mask
       * @param {Integer} tabIndex Tab index
       * @return {Integer} Next row number
       */
      getNextRow: function (docMask, tabIndex) {
        var i,
            maxRow = 0,
            docMaskLine;

        for (i = 0; i < docMask.lines.length; i++) {
          docMaskLine = docMask.lines[i];
          if (docMaskLine.tabIndex == tabIndex) {
            if (docMaskLine.editRow > maxRow) {
              maxRow = docMaskLine.editRow;
            }
          }
        }

        if (maxRow > 0) {
          maxRow++;
        }

        return maxRow;
      },

      reservedKeys: ["_RESERVED", "RESERVED"],

      /**
       * Stores a document mask line
       * @param {de.elo.ix.client.DocMaskLine[]} lines Document mask lines
       * @param {de.elo.ix.client.DocMaskLine} newLine Document mask line
       * @return {Array} Document mask lines
       */
      storeDocMaskLine: function (lines, newLine) {
        var me = this,
            id, maxId = 0,
            i, docMaskLine;

        for (i = 0; i < lines.length; i++) {
          docMaskLine = lines[i];
          id = parseInt(docMaskLine.id, 10);
          if (id > maxId) {
            maxId = id;
          }

          if (me.reservedKeys.indexOf(docMaskLine.key) > -1) {
            newLine.id = docMaskLine.id;
            lines[i] = newLine;
            me.handler.log({ text: "Reuse reserved line: line.id={0}, line.key={1}, newLine.key={2}", data: [docMaskLine.id, docMaskLine.key, newLine.key] });
            return lines;
          }
        }
        newLine.id = maxId + 1;
        if (newLine.id == 50) {
          newLine.id = 60; // skip reserved lines
        }
        if (newLine.id > DocMaskLineC.MAX_ID_DOCMASK_LINE) {
          me.handler.log({ text: "No more lines available: newLine.key={0}, newLine.id={1}", data: [newLine.key, newLine.id] });
          return lines;
        }

        me.handler.log({ text: "Append new line: newLine.key={0}, newLine.id={1}", data: [newLine.key, newLine.id] });
        lines.push(newLine);
        return lines;
      }
    });

    sol.define("sol.dev.install.mixin.UndeployUtils", {
      mixin: true,

      deleteReferences: function (folderId) {
        var me = this,
            sordZ, sords, displayRepoPath;

        sordZ = new SordZ(SordC.mbMin);
        sordZ.add(SordC.mbRefPaths);

        me.handler.log({ text: "Delete references...", statusBar: true });

        sords = sol.common.RepoUtils.findChildren(folderId, { includeFolders: true, includeDocuments: true, recursive: true, level: 20, sordZ: sordZ });
        sords.forEach(function (sord) {
          displayRepoPath = sol.common.SordUtils.getDisplayRepoPath(sord);
          me.handler.log({ text: "Delete references: {0}", data: [displayRepoPath], statusBar: true });
          sol.common.RepoUtils.deleteAllReferences(sord);
        });
      },

      renamePackage: function (packageFolderObjId) {
        var me = this,
            sord, oldName, newName, colorId;

        if (!packageFolderObjId) {
          throw "Package folder object ID is emtpy";
        }

        sord = sol.common.RepoUtils.getSord(packageFolderObjId, { sordZ: SordC.mbMin });
        oldName = sord.name;
        newName = oldName + " - undeployed - " + me.config.base.timestamp;
        if (oldName.indexOf("undeployed") > -1) {
          return;
        }

        me.handler.log({ text: "Rename old package: {0} -> {1}", data: [oldName, newName] });

        sord.name = newName;
        colorId = sol.common.RepoUtils.getColorId("sol.package.undeployed.warn");
        if (colorId) {
          sord.kind = colorId;
        }
        ixConnect.ix().checkinSord(sord, SordC.mbMin, LockC.NO);

        return newName;
      }
    });

    sol.define("sol.dev.install.functions.UndeployPackage", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.UndeployUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return (me.result.undeploy || me.config.install.undeploy);
      },

      /**
       * Deletes the references
       */
      execute: function () {
        var me = this,
            packageFolderPath, packageFolderObjId, newPackageName, undeployRemove;

        packageFolderPath = sol.common.RepoUtils.resolveSpecialFolder("{{packageFolderPath}}", me.config.base.repoPathParamObj);

        me.handler.log({ text: "Undeploy package: {0}", data: [me.config.install.packageName], statusBar: true });

        me.deleteReferences(packageFolderPath, me.config.install.packageName);

        packageFolderObjId = me.config.install.packageFolderId || packageFolderPath;

        packageFolderObjId = sol.common.RepoUtils.getObjId(packageFolderObjId);

        newPackageName = me.renamePackage(packageFolderObjId);

        undeployRemove = (typeof me.config.install.undeployRemove != "undefined") ? me.config.install.undeployRemove : true;

        if (undeployRemove) {
          me.handler.log({ text: "Remove undeployed package: {0}", data: [newPackageName] });
          sol.common.RepoUtils.deleteSord(packageFolderObjId);
        }
      }
    });

    sol.define("sol.dev.install.functions.UndeployHotfix", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.UndeployUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return ((me.result.undeploy || me.config.install.undeploy) && me.config.install.undeployHotfix);
      },

      /**
       * Deletes the references
       */
      execute: function () {
        var me = this,
            hotfixFolderPath, hotfixSectionName, exists;

        hotfixSectionName = me.config.install.hotfixSectionName || "Business Solutions Hotfix";

        me.handler.log({ text: "Undeploy hotfix: {0}", data: [me.config.install.packageName], statusBar: true });

        hotfixFolderPath = sol.common.RepoUtils.resolveSpecialFolder("{{administrationFolderPath}}" + me.pilcrow + hotfixSectionName + me.pilcrow + me.config.install.packageName, me.config.base.repoPathParamObj);

        exists = sol.common.RepoUtils.exists(hotfixFolderPath);
        if (!exists) {
          return;
        }

        me.deleteReferences(hotfixFolderPath, me.config.install.packageName);
        me.renamePackage(hotfixFolderPath);
      }
    });

    sol.define("sol.dev.install.functions.DeployRenamePackage", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return (me.config.install.deploy);
      },

      /**
       * Renames the existing package
       */
      execute: function () {
        var me = this,
            sord, oldName, newName;

        if (!me.config.install.packageFolderId) {
          throw "Package folder ID is empty";
        }

        sord = sol.common.RepoUtils.getSord(me.config.install.packageFolderId, { sordZ: SordC.mbMin });
        oldName = sord.name;
        newName = me.config.install.packageName;

        me.handler.log({ text: "Rename  package: {0} -> {1}", data: [oldName, newName] });
        sord.name = newName;
        sord.kind = 0;

        ixConnect.ix().checkinSord(sord, SordC.mbMin, LockC.NO);
      }
    });

    sol.define("sol.dev.install.functions.ReassignGuids", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.reassignGuids;
      },

      /**
       * Reassign GUIDs
       */
      execute: function () {
        var me = this,
            i, guid;

        me.handler.log({ text: "Reassign GUIDs...", statusBar: true });

        me.guids = me.config.install.reassignGuids;

        if (me.guids == "true") {
          me.guids = [
            "(9FD1AC0B-8744-6326-2EFD-FCC6E32DE515)",
            "(F4CFD23F-5745-367A-FFA1-78E0C19116D8)",
            "(DFD14BA1-4AC0-36E3-89CE-3CF322E3D607)",
            "(B7E96A85-0C63-B6AD-42E5-BA9F0018F01F)",
            "(AD913CFE-BBF2-3D89-48C3-3F156D497BFD)"
          ];
        }

        for (i = 0; i < me.guids.length; i++) {
          guid = me.guids[i];
          me.reassignGuid(guid);
        }
      },

      reassignGuid: function (guid) {
        var me = this,
            sord, isDocument, exceptionString;

        try {
          sord = sol.common.RepoUtils.getSord(guid, { sordZ: SordC.mbMin });
        } catch (ex) {
          return;
        }

        isDocument = sol.common.SordUtils.isDocument(sord);
        if (!isDocument) {
          return;
        }

        me.handler.log({ text: "Reassign GUID: guid=" + sord.guid + ", name=" + sord.name, statusBar: true });

        if (!sord.deleted) {
          me.handler.log({ text: "Copy sord: guid=" + sord.guid + ", name=" + sord.name });
          sol.common.RepoUtils.copySords(sord.id, sord.parentId);
        }

        me.handler.log({ text: "Delete finally: guid=" + sord.guid + ", name=" + sord.name });
        try {
          sol.common.RepoUtils.deleteSord(sord.guid, { deleteFinally: true });
        } catch (ex) {
          exceptionString = sol.common.ExceptionUtils.parseException(ex);
          me.handler.log({ text: "Can't delete sord to reassign GUID: guid=" + sord.guid + ", name=" + sord.name + ", exception=" + exceptionString });
        }
      }
    });

    sol.define("sol.dev.install.functions.ImportRepoData", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Imports the repository data
       */
      execute: function () {
        var me = this;

        me.config.base.importZipFilePath = me.config.base.importZipFilePath || me.handler.payloadDirPath + File.separator + "RepoData.zip";

        me.handler.log({ text: "Import repository data: " + me.config.base.importZipFilePath, statusBar: true });

        if (me.config.install.importDestinationFolderPath) {
          me.config.install.importDestinationFolderPath = sol.common.RepoUtils.resolveSpecialFolder(me.config.install.importDestinationFolderPath, me.config.base.repoPathParamObj);
        } else {
          me.config.install.importDestinationFolderPath = "";
        }

        sol.common.WfUtils.setSessionOptionStartDocMaskWorkflows(false);
        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, false);

        sol.common.RepoUtils.importRepoData(new File(me.config.base.importZipFilePath), me.config.install.importDestinationFolderPath, ImportOptionsC.GUIDS_KEEP, ImportOptionsC.IMPORT_KEYWORDS);

        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, true);
        sol.common.WfUtils.setSessionOptionStartDocMaskWorkflows(true);
      }
    });

    sol.define("sol.dev.install.functions.AdjustDocMaskRights", {
      extend: "sol.dev.install.InstallFunctionBase",

      /**
       * Imports the repository data
       */
      execute: function () {
        var me = this,
            docMaskNames, docMaskName, i, docMask, inheritAclItem;

        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, false);
        docMaskNames = sol.common.SordUtils.getDocMaskNames({ filters: { nameTranslationKeyPrefix: "sol." } });

        inheritAclItem = new AclItem();
        inheritAclItem.type = AclItemC.TYPE_INHERIT;

        for (i = 0; i < docMaskNames.length; i++) {
          docMaskName = docMaskNames[i];

          docMask = ixConnect.ix().checkoutDocMask(docMaskName.id + "", DocMaskC.mbAll, LockC.NO);
          if ((docMask.docAclItems.length == 1) && (docMask.docAclItems[0].id == UserInfoC.ID_EVERYONE_GROUP)) {
            docMask.docAclItems = [inheritAclItem];
            me.handler.log({ text: "Adjust document rights: docMaskName={0}, nameTranslationKey={1}", data: [docMask.name, docMask.nameTranslationKey] });
            ixConnect.ix().checkinDocMask(docMask, DocMaskC.mbAll, LockC.NO);

          }
        }
        sol.common.RepoUtils.setSessionOption(SessionOptionsC.TRANSLATE_TERMS, true);
      }
    });

    sol.define("sol.dev.install.functions.PrepareRepoPaths", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.repoPaths;
      },

      execute: function () {
        var me = this,
            path;

        me.handler.log({ text: "Prepare repository paths...", statusBar: true });
        me.config.install.repoPaths.forEach(function (repoPathSet) {
          path = sol.common.RepoUtils.resolveSpecialFolder(repoPathSet.path, me.config.base.repoPathParamObj);
          me.handler.log({ text: "Prepare repository path: path={0}, rights={1}", data: [path, JSON.stringify(repoPathSet.rightsConfig)] });
          sol.common.RepoUtils.preparePath(path, { rightsConfig: repoPathSet.rightsConfig });
        });
      }
    });

    sol.define("sol.dev.install.functions.ChangeRights", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.changeRights;
      },

      execute: function () {
        var me = this,
            path, objId;

        me.handler.log({ text: "Change rights...", statusBar: true });

        me.config.install.changeRights.forEach(function (rightsEntry) {

          path = sol.common.RepoUtils.resolveSpecialFolder(rightsEntry.path, me.config.base.repoPathParamObj);
          objId = sol.common.RepoUtils.getObjId(path) + "";

          me.handler.log({ text: "Change rights: path=" + path + ", objId=" + objId + ", config=" + JSON.stringify(rightsEntry) });
          try {
            sol.common.AclUtils.changeRightsInBackground(objId, rightsEntry);
          } catch (ex) {
            me.handler.log({ text: "Can't change rights: exception=" + ex });
          }
        });
      }
    });

    sol.define("sol.dev.install.mixin.RefUtils", {
      mixin: true,

      refPrioSectionOrder: ["Business Solutions", "Business Solutions Hotfix", "Other", "Business Solutions Custom", "Original"],

      /**
       * Reference document
       * @param {de.elo.ix.client.Sord} srcSord Source sord
       * @param {String} dstFolderId Destination folder ID
       */
      refDocument: function (srcSord, dstFolderId) {
        var me = this,
            srcDisplayPath, srcObjId, dstFolderDisplayPath, name, dstDisplayPath,
            dstChildEntry, srcSectionName, srcSectionPrio, dstSectionPrio;

        me.refPrioSectionOrder = me.config.install.refPrioSectionOrder || me.refPrioSectionOrder;

        me.dstFolderChildrenCache = me.dstFolderChildrenCache || {};
        if (!me.dstFolderChildrenCache[dstFolderId]) {
          me.addDstChildrenToCache(dstFolderId);
        }

        name = srcSord.name + "";

        me.displayPathCache = me.displayPathCache || {};
        dstFolderDisplayPath = me.displayPathCache[dstFolderId];
        if (!dstFolderDisplayPath) {
          dstFolderDisplayPath = sol.common.RepoUtils.getPathFromObjId(dstFolderId);
          me.displayPathCache[dstFolderId] = dstFolderDisplayPath;
        }
        srcObjId = srcSord.id + "";
        srcDisplayPath = me.displayPathCache[srcObjId];
        if (!srcDisplayPath) {
          srcDisplayPath = sol.common.SordUtils.getDisplayRepoPath(srcSord);
          me.displayPathCache[srcObjId] = srcDisplayPath;
        }

        dstDisplayPath = dstFolderDisplayPath + "/" + name;

        dstChildEntry = me.getDstChildEntryFromCache(dstFolderId, name);

        if (dstChildEntry) {
          srcSectionName = me.getSectionName(dstFolderId, srcSord);
          srcSectionPrio = me.getSectionPrio(srcSectionName);
          dstSectionPrio = me.getSectionPrio(dstChildEntry.srcSectionName);

          me.handler.log({ text: "Entry name already exists in the destination folder: " + dstDisplayPath });

          if (srcSectionPrio <= dstSectionPrio) {
            me.handler.log({ text: "Source section priority '" + srcSectionName + "' <= Destination section priority '" + dstChildEntry.srcSectionName + "' -> Skip creating reference: " + dstDisplayPath });
            return;
          } else {
            if (dstChildEntry.parentId != dstFolderId) {
              me.handler.log({ text: "Source section priority '" + srcSectionName + "' > Destination section priority '" + dstChildEntry.srcSectionName + "' -> Delete existing reference: " + dstDisplayPath });
              ixConnect.ix().deleteSord(dstFolderId, dstChildEntry.objId + "", LockC.NO, new DeleteOptions());
              me.markUnreferencedSrcSord(dstChildEntry.objId);
            }
          }
        }

        srcDisplayPath = sol.common.SordUtils.getDisplayRepoPath(srcSord);
        me.handler.log({ text: "Create reference: " + srcDisplayPath + " -> " + dstFolderDisplayPath, statusBar: true });

        if (dstFolderId == srcSord.id) {
          throw "Source and target object ID must not be identical: objId=" + dstFolderId;
        }

        ixConnect.ix().refSord("", dstFolderId + "", srcSord.id + "", -1);
        me.addDstChildToCache(dstFolderId, srcSord);
      },

      /**
       * Adds the destination children names to the cache
       * @param {String} dstFolderId Destination folder ID
       */
      addDstChildrenToCache: function (dstFolderId) {
        var me = this,
            sordZ, children;

        sordZ = new SordZ(SordC.mbMin);
        sordZ.add(SordC.mbRefPaths);

        children = sol.common.RepoUtils.findChildren(dstFolderId, { recursive: false, level: 1, includeReferences: true, sordZ: sordZ });
        children.forEach(function (child) {
          me.addDstChildToCache(dstFolderId, child);
        });
      },

      /**
       * Adds a destination child name to the cache
       * @param {String} dstFolderId Destination folder ID
       * @param {de.elo.ix.client.Sord} child Child
       */
      addDstChildToCache: function (dstFolderId, child) {
        var me = this,
            childName, srcSectionName, childObjId, childParentId;

        childName = child.name + "";
        me.dstFolderChildrenCache = me.dstFolderChildrenCache || {};
        me.dstFolderChildrenCache[dstFolderId] = me.dstFolderChildrenCache[dstFolderId] || {};
        srcSectionName = me.getSectionName(dstFolderId, child);
        childObjId = child.id + "";
        childParentId = child.parentId + "";
        me.dstFolderChildrenCache[dstFolderId][childName] = { objId: childObjId, srcSectionName: srcSectionName, parentId: childParentId };
      },

      /**
       * Returns the package name
       * @param {String} dstFolderId Destination folder ID
       * @param {de.elo.ix.client.Sord} sord Sord
       * @return {String} Package name
       */
      getSectionName: function (dstFolderId, sord) {
        var me = this,
            sectionNameIndex = 1,
            sectionName = "Other",
            repoPathParts;

        if (dstFolderId == sord.parentId) {
          return "Original";
        }

        if (!sord.refPaths || (sord.refPaths.length == 0)) {
          me.handler.log({ text: "Can't detect section name: Refpaths are empty: sord.id=" + sord.id + ", sord.name=" + sord.name });
          return sectionName;
        }

        if (!sord.refPaths[0].path || (sord.refPaths[0].path.length <= sectionNameIndex)) {
          return sectionName;
        }

        repoPathParts = sord.refPaths[0].path;
        sectionName = repoPathParts[sectionNameIndex].name + "";

        sectionName = (me.refPrioSectionOrder.indexOf(sectionName) > -1) ? sectionName : "Other";

        return sectionName;
      },

      getSectionPrio: function (sectionName) {
        var me = this,
            index;

        index = me.refPrioSectionOrder.indexOf(sectionName);
        return index;
      },

      /**
       * Checks whether the name exists in the destination child name cache
       * @param {String} dstFolderId Destination folder ID
       * @param {String} childName Destionation child name
       * @return {Boolean}
       */
      getDstChildEntryFromCache: function (dstFolderId, childName) {
        var me = this,
            exists;

        exists = (!!me.dstFolderChildrenCache && !!me.dstFolderChildrenCache[dstFolderId] && !!me.dstFolderChildrenCache[dstFolderId][childName]);
        if (exists) {
          return me.dstFolderChildrenCache[dstFolderId][childName];
        }
      },

      /**
       * Marks unreferenced source Sords
       * @param {String} objId
       */
      markUnreferencedSrcSord: function (objId) {
        var me = this,
            srcSord;

        srcSord = ixConnect.ix().checkoutSord(objId + "", new SordZ(SordC.mbRefPaths), LockC.NO);

        if (srcSord.refPaths.length > 1) {
          return;
        }

        me.colorDisabled = me.colorDisabled || sol.common.RepoUtils.getColorId("sol.common.disabled");

        if (typeof me.colorDisabled != "undefined") {
          srcSord.kind = me.colorDisabled;
          ixConnect.ix().checkinSord(srcSord, new SordZ(ObjDataC.mbKind), LockC.NO);
        }
      }
    });

    sol.define("sol.dev.install.functions.CreateChildrenReferences", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.RefUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return !!me.config.install.refChildren;
      },

      /**
       * Creates references of documents which are located in specified folders
       */
      execute: function () {
        var me = this;

        me.handler.log({ text: "Create children references...", statusBar: true });
        me.config.install.refChildren.forEach(function (refChildrenEntry) {
          me.processReferenceChildrenEntry(refChildrenEntry);
        });
      },

      /**
       * @private
       * * Processes the repository references of a single source folder
       * @param {Object} refChildrenEntry
       */
      processReferenceChildrenEntry: function (refChildrenEntry) {
        var me = this,
            srcPath, startFolder, findConfig, children;

        srcPath = sol.common.RepoUtils.resolveSpecialFolder(refChildrenEntry.source, me.config.base.repoPathParamObj);
        srcPath = sol.common.RepoUtils.normalizePath(srcPath, true);
        try {
          startFolder = ixConnect.ix().checkoutSord(srcPath + "", EditInfoC.mbSord, LockC.NO).sord;
        } catch (e) {
          me.handler.log({ text: "Source path not found: " + srcPath, statusBar: true });
          return;
        }

        if (refChildrenEntry.refFolders) {
          findConfig = { recursive: false, level: 1, includeDocuments: false, includeFolders: true };
        } else {
          findConfig = { recursive: true, level: 10, includeDocuments: true, includeFolders: false };
        }

        children = sol.common.RepoUtils.findChildren(startFolder.id, findConfig);
        children.forEach(function (child) {
          if ((refChildrenEntry.exclude) && (refChildrenEntry.exclude.indexOf(String(child.name)) > -1)) {
            return;
          }
          me.processReferenceChild(startFolder, child, refChildrenEntry);
        });
      },

      /**
       * @private
       * Builds the destination repository paths of a reference and creates them
       * @param {de.elo.ix.client.Sord} startFolder
       * @param {de.elo.ix.client.Sord} child
       * @param {Object} refChildrenEntry
       */
      processReferenceChild: function (startFolder, child, refChildrenEntry) {
        var me = this,
            i, j, dstFolderPathParts, dstFolderPath, childPathParts,
            destinations, startFolderPathPartsLen, dstFolderId;

        destinations = refChildrenEntry.destinations;
        startFolderPathPartsLen = startFolder.refPaths[0].path.length;
        for (i = 0; i < destinations.length; i++) {
          dstFolderPathParts = [sol.common.RepoUtils.resolveSpecialFolder(destinations[i], me.config.base.repoPathParamObj)];
          childPathParts = child.refPaths[0].path;
          if (!refChildrenEntry.ignoreSubfolders) {
            for (j = startFolderPathPartsLen + 1; j < childPathParts.length; j++) {
              dstFolderPathParts.push(childPathParts[j].name);
            }
          }
          dstFolderPath = dstFolderPathParts.join(me.pilcrow);

          dstFolderId = sol.common.RepoUtils.preparePath(dstFolderPath);
          me.refDocument(child, dstFolderId);
        }
      }
    });

    sol.define("sol.dev.install.functions.CreateSingleReferences", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.RefUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return !!me.config.install.ref;
      },

      /**
       * Creates references of defined documents and folders
       */
      execute: function () {
        var me = this;

        me.handler.log({ text: "Create single references...", statusBar: true });
        me.config.install.ref.forEach(function (refEntry) {
          me.processRefEntry(refEntry);
        });
      },

      /**
       * @private
       * Processes the creation of a single reference
       * @param {Object} refEntry
       */
      processRefEntry: function (refEntry) {
        var me = this,
            srcPath, srcObjId, sordZ, srcSord, dstFolderId;

        srcPath = sol.common.RepoUtils.resolveSpecialFolder(refEntry.source, me.config.base.repoPathParamObj);
        srcObjId = sol.common.RepoUtils.getObjId(srcPath);

        if (!srcObjId) {
          me.handler.log({ text: "Source repository path not found: " + srcPath, statusBar: true });
          return;
        }

        sordZ = new SordZ(SordC.mbMin);
        sordZ.add(SordC.mbRefPaths);
        srcSord = ixConnect.ix().checkoutSord(srcObjId + "", sordZ, LockC.NO);

        refEntry.destinations.forEach(function (destination) {
          destination = sol.common.RepoUtils.resolveSpecialFolder(destination, me.config.base.repoPathParamObj);
          dstFolderId = sol.common.RepoUtils.preparePath(destination);

          me.refDocument(srcSord, dstFolderId);
        });

        me.markUnreferencedSrcSord(srcSord.id);
      }
    });

    sol.define("sol.dev.install.functions.AddMemberships", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.memberships;
      },

      /**
       * Adds memberships to users or groups
       */
      execute: function () {
        var me = this,
            i, membership;

        me.handler.log({ text: "Add memberships...", statusBar: true });
        for (i = 0; i < me.config.install.memberships.length; i++) {
          membership = me.config.install.memberships[i];
          try {
            sol.common.UserUtils.addUsersToGroups(membership.names, membership.memberOf);
            me.handler.log({ text: "Add users/groups " + membership.names + " added to group(s): " + membership.memberOf, statusBar: true });
          } catch (ex) {
            me.handler.log({ text: "Can't add users/groups " + membership.names + " to group(s): " + membership.memberOf + ": " + ex, statusBar: false });
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.RemoveMemberships", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.removeMemberships;
      },

      /**
       * Adds memberships to users or groups
       */
      execute: function () {
        var me = this,
            i, membership;

        me.handler.log({ text: "Remove memberships...", statusBar: true });
        for (i = 0; i < me.config.install.removeMemberships.length; i++) {
          membership = me.config.install.removeMemberships[i];
          try {
            sol.common.UserUtils.removeUsersFromGroups(membership.names, membership.memberOf);
            me.handler.log({ text: "Remove users/groups " + membership.names + " from group(s): " + membership.memberOf, statusBar: true });
          } catch (ex) {
            me.handler.log({ text: "Can't remove users/groups " + membership.names + " from group(s): " + membership.memberOf + ": " + ex, statusBar: false });
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.CreateColors", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.colors;
      },

      /**
       * Creates the specified colors
       */
      execute: function () {
        var me = this;

        me.handler.log({ text: "Create colors...", statusBar: true });
        sol.common.RepoUtils.addColors(me.config.install.colors);
      }
    });

    sol.define("sol.dev.install.functions.InstallWorkflowTemplateFiles", {
      extend: "sol.dev.install.InstallFunctionBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
        me.prepareWorkflowTemplates();
      },

      check: function () {
        var me = this;

        return (me.workflowTemplates.length > 0);
      },

      workflowTimestampSeparator: " | ",

      execute: function () {
        var me = this,
            i, workflowTemplate, workflowTemplateFile, exceptionString, importWorkflowTemplateResult;

        me.handler.log({ text: "Install workflow template files...", statusBar: true });

        for (i = 0; i < me.workflowTemplates.length; i++) {
          workflowTemplate = me.workflowTemplates[i];
          if (workflowTemplate.import) {
            try {
              me.handler.log({ text: "Install workflow template file'" + workflowTemplate.name + "'...", statusBar: true });
              me.handler.log({ text: "Included workflow templates: [" + workflowTemplate.includedWorkflowTemplates + "]", statusBar: false });
              me.renameWorkflowTemplates(workflowTemplate.includedWorkflowTemplates);
              workflowTemplateFile = new File(workflowTemplate.path);
              importWorkflowTemplateResult = sol.common.WfUtils.importWorkflow(workflowTemplate.name, workflowTemplateFile);
              me.handler.log({ text: "Workflow template file '" + workflowTemplate.name + "' imported.", statusBar: true });
              me.handler.log({ text: "importResult=" + JSON.stringify(importWorkflowTemplateResult), statusBar: false });
              me.mergeWorkflowTemplates(workflowTemplate.includedWorkflowTemplates);
              me.adjustSubWorkflowNodes(workflowTemplate.includedWorkflowTemplates);
              me.handler.log({ text: "Installation of workflow template file '" + workflowTemplate.name + "' completed.", statusBar: false });
            } catch (ex) {
              exceptionString = sol.common.ExceptionUtils.parseException(ex);
              me.handler.log({ text: "Can't import workflow '" + workflowTemplate.name + "' :" + exceptionString });
            }
          }
        }
      },

      prepareWorkflowTemplates: function () {
        var me = this,
            workflowTemplateFilter, workflowTemplates, workflowTemplatesDirPath, workflowTemplatesDir, workflowFileIterator,
            workflowTemplateNames, workflowFile, workflowName, workflowJsonContent, exceptionString, key;

        me.workflowTemplates = [];
        workflowTemplatesDirPath = me.handler.payloadDirPath + File.separator + "workflowTemplates";
        workflowTemplatesDir = new File(workflowTemplatesDirPath);

        if (workflowTemplatesDir.exists()) {
          workflowTemplateFilter = me.config.build.workflowTemplates || [];
          workflowTemplates = {};

          workflowFileIterator = Packages.org.apache.commons.io.FileUtils.iterateFiles(workflowTemplatesDir, ["json"], false);
          while (workflowFileIterator.hasNext()) {
            workflowFile = workflowFileIterator.next();
            try {
              workflowName = String(sol.common.FileUtils.removeExtension(workflowFile.name));
              if (workflowTemplateFilter.length === 0 || workflowTemplateFilter.indexOf(workflowName) > -1) {
                workflowJsonContent = sol.common.FileUtils.readFileToString(workflowFile.canonicalPath);
                workflowTemplateNames = sol.common.WfUtils.getAllWorkflowNamesFromJson(workflowJsonContent);

                workflowTemplateNames.forEach(function (templateName) { // eslint-disable-line no-loop-func
                  // we'll mark the base workflow with true (to make sure it'll be imported)
                  // all contained sub workflow will be marked with "false". We will ignore those during the import
                  workflowTemplates[templateName] = {
                    name: templateName,
                    path: workflowTemplatesDirPath + File.separator + templateName + ".json",
                    import: workflowTemplates.hasOwnProperty(templateName) ? false : (templateName == workflowName),
                    order: workflowTemplateFilter.indexOf(workflowName)
                  };
                });

                workflowTemplates[workflowName].includedWorkflowTemplates = workflowTemplateNames;
              }
            } catch (ex) {
              exceptionString = sol.common.ExceptionUtils.parseException(ex);
              me.handler.log({ text: "Can't collect sub workflow template names within workflow template file '" + workflowFile.canonicalPath + "' :" + exceptionString });
            }
          }

          // convert to an array
          for (key in workflowTemplates) {
            me.workflowTemplates.push(workflowTemplates[key]);
          }

          me.workflowTemplates.sort(function (a, b) {
            return a.order - b.order;
          });
        }
      },

      renameWorkflowTemplates: function (workflowTemplateNames) {
        var me = this,
            renamedWorkflowTemplates = {},
            i, workflowTemplateName, newWorkflowTemplateName, renameResult;

        for (i = 0; i < workflowTemplateNames.length; i++) {
          workflowTemplateName = workflowTemplateNames[i];
          newWorkflowTemplateName = workflowTemplateName + me.workflowTimestampSeparator + "origin - " + me.config.base.timestamp;
          renameResult = sol.common.WfUtils.renameWorkflowTemplate(workflowTemplateName, newWorkflowTemplateName);
          if (renameResult) {
            me.handler.log({ text: "Origin workflow template renamed: '" + workflowTemplateName + "' -> '" + newWorkflowTemplateName + "'", statusBar: true });
            me.handler.log({ text: "renameResult=" + JSON.stringify(renameResult) });
            renamedWorkflowTemplates[renameResult.oldName] = renameResult.wfTplId;
          }
        }

        return renamedWorkflowTemplates;
      },

      mergeWorkflowTemplates: function (workflowTemplateNames) {
        var me = this,
            i, workflowTemplateName, mergeTemplateResult;

        for (i = 0; i < workflowTemplateNames.length; i++) {
          workflowTemplateName = workflowTemplateNames[i];
          mergeTemplateResult = sol.common.WfUtils.mergeWorkflowTemplate(workflowTemplateName);
          if (mergeTemplateResult) {
            me.handler.log({ text: "Workflow template '" + mergeTemplateResult.wfTplName + "' merged.", statusBar: true });
            me.handler.log({ text: "mergeTemplateResult=" + JSON.stringify(mergeTemplateResult) });
          }
        }
      },

      adjustSubWorkflowNodes: function (workflowTemplateNames) {
        var me = this,
            wfDiag, i, j, workflowTemplateName, propertiesObj, subTemplateDiag, node, subTemplateName, subTemplateId, wfDiagChanged;

        for (i = 0; i < workflowTemplateNames.length; i++) {

          workflowTemplateName = workflowTemplateNames[i];

          wfDiag = ixConnect.ix().checkoutWorkflowTemplate(workflowTemplateName, "", WFDiagramC.mbAll, LockC.NO);

          wfDiagChanged = false;

          for (j = 0; j < wfDiag.nodes.length; j++) {
            node = wfDiag.nodes[j];

            if (node.type != WFNodeC.TYPE_CALL_SUB_WORKFLOW) {
              continue;
            }

            try {
              propertiesObj = JSON.parse(node.properties);
            } catch (ex) {
              me.handler.log({ text: "Can't parse sub workflow node properties: workflowTemplateName=" + workflowTemplateName + ", node.name=" + node.name + ", node.properties=" + node.properties });
              continue;
            }

            subTemplateName = propertiesObj.subTemplateName;

            if (subTemplateName) {
              try {
                subTemplateDiag = ixConnect.ix().checkoutWorkflowTemplate(subTemplateName, "", WFDiagramC.mbAll, LockC.NO);
              } catch (ex) {
                me.handler.log({ text: "Can't find sub workflow template: tplName=" + wfDiag.name + ", nodeName=" + node.name + ", subTplName=" + subTemplateName, statusBar: false });
                continue;
              }
              subTemplateId = subTemplateDiag.id;
              node.subTemplateId = subTemplateId;
              me.handler.log({ text: "Sub workflow template ID set: tplName=" + wfDiag.name + ", nodeName=" + node.name + ", subTplName=" + subTemplateName + ", subTplId=" + subTemplateId, statusBar: false });
              wfDiagChanged = true;
            }
          }

          if (wfDiagChanged) {
            ixConnect.ix().checkinWorkFlow(wfDiag, WFDiagramC.mbAll, LockC.NO);
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.UninstallIxPlugins", {
      extend: "sol.dev.install.InstallFunctionBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this,
            currentIxVersion;

        currentIxVersion = ixConnect.version;

        if (!sol.common.RepoUtils.checkVersion(currentIxVersion, "11.00.000")) {
          return false;
        }

        return (me.config.install.uninstallIxPlugins && me.config.install.uninstallIxPlugins.symbolicNamePrefixes);
      },

      /**
       * Uninstalls IX plug-ins
       */
      execute: function () {
        var me = this,
            uninstallPlugins = [],
            prefixes, i, prefix, pluginList, pluginListIterator, plugin, pluginSymbolicName, exceptionString;

        me.handler.log({ text: "Uninstall IX plug-ins...", statusBar: true });

        prefixes = me.config.install.uninstallIxPlugins.symbolicNamePrefixes;

        pluginList = ixConnect.pluginService.getPlugins();

        me.handler.log({ text: "Currently installed plug-ins:", statusBar: false });

        pluginListIterator = pluginList.iterator();

        while (pluginListIterator.hasNext()) {
          plugin = pluginListIterator.next();
          me.handler.log({ text: "id=" + plugin.id + ", symbolicName=" + plugin.symbolicName + ", state=" + plugin.state, statusBar: false });
        }

        pluginListIterator = pluginList.iterator();

        while (pluginListIterator.hasNext()) {
          plugin = pluginListIterator.next();
          pluginSymbolicName = plugin.symbolicName + "";
          for (i = 0; i < prefixes.length; i++) {
            prefix = prefixes[i];
            if (pluginSymbolicName.indexOf(prefix) == 0) {
              uninstallPlugins.push(plugin);
            }
          }
        }

        for (i = 0; i < uninstallPlugins.length; i++) {
          plugin = uninstallPlugins[i];
          try {
            ixConnect.pluginService.uninstall(plugin.id);
            me.handler.log({ text: "IX plug-in '" + plugin.symbolicName + "' uninstalled." });
          } catch (ex) {
            exceptionString = sol.common.ExceptionUtils.parseException(ex);
            me.handler.log({ text: "Can't uninstall IX plug-in: name=" + plugin.symbolicName + ", exception=" + exceptionString });
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.InstallIxPlugins", {
      extend: "sol.dev.install.InstallFunctionBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
        me.ixPluginsDirPath = me.handler.payloadDirPath + File.separator + "IX Plug-ins";
        me.ixPluginsDir = new File(me.ixPluginsDirPath);
        me.ixPluginFiles = me.ixPluginsDir.listFiles();
      },

      check: function () {
        var me = this,
            currentIxVersion;

        currentIxVersion = ixConnect.version;

        if (!sol.common.RepoUtils.checkVersion(currentIxVersion, "11.00.000")) {
          return false;
        }

        return (me.ixPluginFiles && (me.ixPluginFiles.length > 0));
      },

      /**
       * Installs IX plug-ins
       */
      execute: function () {
        var me = this,
            i, ixPluginFile, inputStream, ixPluginFileName, pluginId, exceptionString;

        me.handler.log({ text: "Install IX plug-ins...", statusBar: true });

        for (i = 0; i < me.ixPluginFiles.length; i++) {
          ixPluginFile = me.ixPluginFiles[i];
          ixPluginFileName = ixPluginFile.name + "";

          inputStream = new FileInputStream(ixPluginFile);

          try {
            pluginId = ixConnect.pluginService.upload(inputStream);
            me.handler.log({ text: "IX plug-in '" + ixPluginFileName + "' installed." });
          } catch (ex) {
            exceptionString = sol.common.ExceptionUtils.parseException(ex);
            me.handler.log({ text: "Can't install IX plug-in: name=" + ixPluginFileName + ", exception=" + exceptionString });
            continue;
          }

          try {
            ixConnect.pluginService.start(pluginId);
            me.handler.log({ text: "IX plug-in '" + ixPluginFileName + "' (ID " + pluginId + ") started." });
          } catch (ex) {
            exceptionString = sol.common.ExceptionUtils.parseException(ex);
            me.handler.log({ text: "Can't start IX plug-in: name=" + ixPluginFileName + ", id=" + pluginId + ", exception=" + exceptionString });
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.CreateSordTypes", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.InstallUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
        me.sordTypesDir = new File(me.handler.payloadDirPath + File.separator + "Sord types");
      },

      check: function () {
        var me = this;

        return me.sordTypesDir.exists();
      },

      /**
       * @private
       * Creates sord types
       */
      execute: function () {
        var me = this,
            sordTypeDirs, i, sordTypeDir, force;

        me.handler.log({ text: "Create sord types...", statusBar: true });

        force = (me.procName == "transport") ? true : false;

        sordTypeDirs = me.sordTypesDir.listFiles();
        for (i = 0; i < sordTypeDirs.length; i++) {
          sordTypeDir = sordTypeDirs[i];
          me.processCreateSordType(sordTypeDir, force);
        }

        me.sendHttpRequest("Refresh web sord type icons", "{{eloWfBaseUrl}}/apps/rest/cmd/refreshicons/", { method: "post", readTimeout: 30000 });
      },

      /**
       * @private
       * Processes the creation of a sord type
       * @param {java.io.File} sordTypeDir
       * @param {Boolean} force Force
       */
      processCreateSordType: function (sordTypeDir, force) {
        var me = this,
            sordTypeDirPath,
            configFilePath, config, result, exceptionString;

        sordTypeDirPath = sordTypeDir.absolutePath;
        configFilePath = sordTypeDirPath + File.separator + "config.json";
        config = sol.common.FileUtils.readConfig(configFilePath);

        try {
          result = sol.common.SordTypeUtils.createSordType(config.id, config.name, config.kind,
            [
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Icon.ico"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Icon.bmp"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Icon.jpg"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Icon.png")
            ],
            [
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Disabled icon.ico"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Disabled icon.bmp"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Disabled icon.jpg"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Disabled icon.png")
            ],
            [
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Link icon.ico"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Link icon.bmp"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Link icon.jpg"),
              sol.common.FileUtils.loadToFileData(sordTypeDir + File.separator + "Link icon.png")
            ],
            config.extensions, force);
          if (result) {
            me.handler.log({ text: "Sord type '" + config.name + "' created.", statusBar: true });
          }
        } catch (ex) {
          exceptionString = sol.common.ExceptionUtils.parseException(ex);
          me.handler.log({ text: "Can't create sord type: sordType=" + config.name + ", exception=" + exceptionString });
        }
      }
    });

    sol.define("sol.dev.install.functions.AssignSordTypes", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.setSordTypes;
      },

      execute: function () {
        var me = this,
            sordTypeSet, objId, i, exceptionString;

        me.handler.log({ text: "Set sord types...", statusBar: true });
        for (i = 0; i < me.config.install.setSordTypes.length; i++) {
          sordTypeSet = me.config.install.setSordTypes[i];
          try {
            objId = sol.common.RepoUtils.resolveSpecialFolder(sordTypeSet.path, me.config.base.repoPathParamObj);
            sol.common.SordTypeUtils.setSordType(objId, sordTypeSet.name);
          } catch (ex) {
            exceptionString = sol.common.ExceptionUtils.parseException(ex);
            me.handler.log({ text: "Can't set sord type: objId=" + objId + ", sordType=" + sordTypeSet.name + ", exception=" + exceptionString });
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.DeleteObjects", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.deleteSords;
      },

      /**
       * Delete sords
       */
      execute: function () {
        var me = this,
            i, repoPath, sord, displayRepoPath;

        me.handler.log({ text: "Delete objects...", statusBar: true });
        for (i = 0; i < me.config.install.deleteSords.length; i++) {
          sord = me.config.install.deleteSords[i];
          repoPath = sol.common.RepoUtils.resolveSpecialFolder(sord, me.config.base.repoPathParamObj);
          repoPath = sol.common.RepoUtils.normalizePath(repoPath, true);
          if (sol.common.RepoUtils.exists(repoPath)) {
            displayRepoPath = sol.common.RepoUtils.getPathFromObjId(repoPath);
            me.handler.log({ text: "Delete object: " + displayRepoPath, statusBar: true });
            sol.common.RepoUtils.deleteSord(repoPath, { deleteFinally: true });
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.UpdateSords", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.updateSords;
      },

      /**
       * Update sords
       */
      execute: function () {
        var me = this,
            i, updateEntry, objId, changes, repoPath, displayRepoPath, sord, mapEntries;

        me.handler.log({ text: "Update objects...", statusBar: true });
        for (i = 0; i < me.config.install.updateSords.length; i++) {
          updateEntry = me.config.install.updateSords[i];
          objId = updateEntry.objId;

          repoPath = sol.common.RepoUtils.resolveSpecialFolder(objId, me.config.base.repoPathParamObj);
          repoPath = sol.common.RepoUtils.normalizePath(repoPath, true);
          objId = sol.common.RepoUtils.getObjId(repoPath);

          changes = updateEntry.changes;

          if (sol.common.RepoUtils.exists(objId)) {
            displayRepoPath = sol.common.RepoUtils.getPathFromObjId(objId);
            me.handler.log({ text: "Update object '{0}': {1}", data: [displayRepoPath, JSON.stringify(changes)] });
            sord = ixConnect.ix().checkoutSord(objId + "", SordC.mbAllIndex, LockC.NO);
            mapEntries = sol.common.SordUtils.updateSord(sord, changes, { silent: true });
            if (mapEntries) {
              ixConnect.ix().checkinMap(MapDomainC.DOMAIN_SORD, objId, objId, mapEntries, LockC.NO);
            }
            ixConnect.ix().checkinSord(sord, SordC.mbAllIndex, LockC.NO);
          }
        }
      }
    });

    sol.define("sol.dev.install.functions.SetPreviewForms", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.previewForms;
      },

      /**
       * Adds preview forms
       */
      execute: function () {
        var me = this;

        me.handler.log({ text: "Set preview forms...", statusBar: true });
        me.readPreviewForms();
        me.config.install.previewForms.forEach(function (previewFormSet) {
          me.processPreviewFormSet(previewFormSet);
        });
        me.writePreviewForms();
      },

      pilcrow: "\u00b6",
      maskFormsKey: "ELOwf.maskforms",

      /**
       * @private
       * Read preview forms
       */
      readPreviewForms: function () {
        var me = this,
            entries, value, entryParts, i, maskKey, form;

        me.previewForms = {};
        me.userProfile = sol.create("sol.common.UserProfile", { userId: UserProfileC.USERID_ALL });
        value = me.userProfile.getOption(me.maskFormsKey);
        entries = value.split(me.pilcrow);
        for (i = 0; i < entries.length; i++) {
          entryParts = entries[i].split(":");
          if ((entryParts.length < 2) || (entryParts[0].length < 2)) {
            continue;
          }
          maskKey = entryParts[0];
          form = entryParts[1];
          if (maskKey.charAt(0) != "(") {
            try {
              maskKey = sol.common.SordUtils.getDocMaskGuid(maskKey);
            } catch (ex) {
              me.handler.log({ text: "Mask '{0}' not found.", data: [maskKey] });
            }
          }
          if (maskKey) {
            me.previewForms[maskKey] = form;
          }
        }
      },

      /**
       * @private
       * Processes the setting of a preview form
       * @param {Object} previewFormSet
       */
      processPreviewFormSet: function (previewFormSet) {
        var me = this,
            maskGuid;

        try {
          maskGuid = sol.common.SordUtils.getDocMaskGuid(previewFormSet.mask);
        } catch (ignore) {
        }

        if (!maskGuid) {
          me.handler.log({ text: "Mask '{0}' not found.", data: [previewFormSet.mask] });
          return;
        }

        me.previewForms[maskGuid] = previewFormSet.form;
        me.handler.log({ text: "Set preview form for mask '{0}' {1}: {2}", data: [previewFormSet.mask, maskGuid, previewFormSet.form] });
      },

      writePreviewForms: function () {
        var me = this,
            entries = [],
            key, optValue;

        for (key in me.previewForms) {
          entries.push(key + ":" + me.previewForms[key]);
        }

        optValue = entries.join(me.pilcrow);
        me.handler.log({ text: "Write global profile option '{0}': {1}", data: [me.maskFormsKey, optValue] });
        me.userProfile.setOption(me.maskFormsKey, entries.join(me.pilcrow));
        me.userProfile.write();
      }
    });

    sol.define("sol.dev.install.functions.SetProfileOpts", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return !!me.config.install.profileOpts;
      },

      /**
       * Adds preview forms
       */
      execute: function () {
        var me = this;

        me.handler.log({ text: "Set profile options...", statusBar: true });

        me.config.install.profileOpts.forEach(function (profileOptsEntry) {
          me.processProfileOptsEntry(profileOptsEntry);
        });
      },

      previewKey: "EloJ.S.PreviewMappingModel",

      MAXIMIZED_BOTH: "6",

      /**
       * @private
       * Sets profile options
       * @param {Object} profileOptsEntry Profile options entry
       */
      processProfileOptsEntry: function (profileOptsEntry) {
        var me = this,
            i = 0,
            userProfile, extension, entry, option;

        me.handler.log({ text: "Set profile options for user '{0}': {1}", data: [profileOptsEntry.user, JSON.stringify(profileOptsEntry)] });

        userProfile = sol.create("sol.common.UserProfile", { userId: profileOptsEntry.user });

        if (profileOptsEntry.documentPreviews) {
          for (extension in profileOptsEntry.documentPreviews) {
            entry = profileOptsEntry.documentPreviews[extension];
            option = extension + ":" + entry.className + ":" + entry.confirm;
            userProfile.setOption(me.previewKey + i, option);
            i++;
          }
          userProfile.setOption(me.previewKey + i, me.pilcrow + "END" + me.pilcrow);
        }

        if (profileOptsEntry.skipTips) {
          userProfile.setOption("EloJ.B.ShowDYK", "false");
        }

        if (profileOptsEntry.maximized) {
          userProfile.setOption("EloJ.S.DlgSize.Workspace.0", "0,0,200,200," + me.MAXIMIZED_BOTH);
        }

        if (profileOptsEntry.entries) {
          for (i = 0; i < profileOptsEntry.entries.length; i++) {
            entry = profileOptsEntry.entries[i];
            userProfile.setOption(entry.key, entry.value);
          }
        }

        userProfile.write();
      }
    });

    sol.define("sol.dev.install.functions.ConfigureAsUrl", {
      extend: "sol.dev.install.InstallFunctionBase",

      check: function () {
        var me = this;

        return (!!me.config.install.asConfigDialog && !!me.config.install.asConfigDialog.configDestination);
      },

      /**
       * Shows an dialog to configure the ELOas URL
       */
      execute: function () {
        var me = this,
            panel, result, window;

        me.asUrl = me.findAsBaseUrl();

        if (me.config.base.scriptEnvironment == "JC") {

          me.dialog = workspace.createGridDialog(me.handler.getText("sol.dev.install.title.asConfig"), 8, 3);
          me.dialog.dialogId = "ConfigureAsUrl-" + java.lang.System.currentTimeMillis().toString();

          panel = me.dialog.gridPanel;
          panel.addLabel(1, 1, 1, me.handler.getText("sol.dev.install.label.asUrl"));
          me.txtAsUrl = panel.addTextField(2, 1, 6);
          me.txtAsUrl.text = me.asUrl;
          panel.addButton(8, 1, 1, me.handler.getText("sol.dev.install.button.urlCheck"), "onAsUrlCheckButtonClicked");
          me.checkAsBaseUrl();

          window = javax.swing.SwingUtilities.getWindowAncestor(panel);
          window.setMinimumSize(new java.awt.Dimension(1000, 100));

          result = me.dialog.show();
          if (result) {
            me.asUrl = (me.txtAsUrl.text || "") + "";
          }
        }

        me.updateAsConfig();
      },

      /**
       * @private
       * Finds the ELOas base URL
       */
      findAsBaseUrl: function () {
        var me = this,
            tries = 0,
            maxTries = 4,
            ixUrl, asProxyUrl, asProxyHttpResponse,
            asUrl, testAsUrl, match, httpResponse, port;

        me.handler.log({ text: "Configure ELOas URL...", statusBar: true });

        ixUrl = ixConnect.endpointUrl + "";
        asProxyUrl = ixUrl.substring(0, ixUrl.length - 3) + "/plugin/de.elo.ix.plugin.proxy/as/";

        asProxyHttpResponse = sol.common.AsUtils.testAsBaseUrl(asProxyUrl);
        me.handler.log({ text: "Test ELOas proxy URL: {0} -> ok={1}, error={2}", data: [asProxyUrl, asProxyHttpResponse.asUrlTestOk, asProxyHttpResponse.errorMessage || ""] });

        if (asProxyHttpResponse.asUrlTestOk) {
          return asProxyUrl;
        }

        asUrl = sol.common.AsUtils.guessAsBaseUrl();
        testAsUrl = asUrl;
        do {
          tries++;
          httpResponse = sol.common.AsUtils.testAsBaseUrl(testAsUrl);
          me.handler.log({ text: "Test ELOas URL: {0} -> ok={1}, error={2}", data: [testAsUrl, httpResponse.asUrlTestOk, httpResponse.errorMessage || ""] });
          if (httpResponse.asUrlTestOk) {
            asUrl = testAsUrl;
          } else {
            match = testAsUrl.match(/(?:\:)(\d{2,5})/);
            port = (match && (match.length == 2)) ? (parseInt(match[1], 10) - 10) : 0;
            testAsUrl = asUrl.replace(/(?:\:\d{2,5})/, ":" + port);
          }
        } while (!httpResponse.asUrlTestOk && port && (tries < maxTries));

        return asUrl;
      },

      /**
       * @private
       * Checks the ELOas URL
       */
      checkAsBaseUrl: function () {
        var me = this,
            httpResponse;

        me.asUrl = me.txtAsUrl.text;
        httpResponse = sol.common.AsUtils.testAsBaseUrl(me.asUrl);
        if (httpResponse.asUrlTestOk) {
          me.txtAsUrl.state = CONSTANTS.FIELD_STATE.CHECKED;
          me.dialog.setStatusNormal(me.handler.getText("sol.dev.install.statusBar.asUrlOk"));
        } else {
          me.txtAsUrl.state = CONSTANTS.FIELD_STATE.INVALID;
          if (httpResponse.errorMessage && httpResponse.errorMessage.message) {
            me.dialog.setStatusRed(httpResponse.errorMessage.message);
          } else {
            me.dialog.setStatusRed(me.handler.getText("sol.dev.install.statusBar.httpStatusCode").replace("{0}", httpResponse.responseCode));
          }
        }
      },

      /**
       * Updates the ELOas configuration
       */
      updateAsConfig: function () {
        var me = this,
            parentPath = "",
            name = "",
            asConfigObj, path, objId;

        me.handler.log({ text: "ELOas URL: " + me.asUrl, statusBar: true });
        asConfigObj = sol.common.AsUtils.convertAsUrlToConfigObject(me.asUrl);

        asConfigObj.readTimeout = 60000;

        me.handler.log({ text: "ELOas configuration: " + JSON.stringify(asConfigObj) });

        path = me.config.install.asConfigDialog.configDestination = me.config.install.asConfigDialog.configDestination || "ARCPATH[(E10E1000-E100-E100-E100-E10E10E10E00)]:/Business Solutions/common/Configuration/as.config";
        path = sol.common.RepoUtils.resolveSpecialFolder(path, me.config.base.repoPathParamObj);

        objId = sol.common.RepoUtils.getObjId(path);

        if (!objId) {
          parentPath = sol.common.RepoUtils.getParentPath(path);
          name = sol.common.RepoUtils.getNameFromPath(path);
        }

        me.handler.log({ text: "Update ELOas config: objId={0}, parentPath={1}, name={2}, content={3}", data: [objId, parentPath, name, JSON.stringify(asConfigObj)] });

        sol.common.RepoUtils.saveToRepo({
          objId: objId,
          name: name,
          parentId: parentPath,
          maskId: "ELO Business Solution Configuration",
          extension: "json",
          objKeysObj: {
            BS_CONFIG_NAME: "ELO AS settings",
            BS_CONFIG_VERSION: "1.0"
          },
          contentObject: asConfigObj,
          withoutBom: true,
          tryUpdate: true
        });
      }
    });

    sol.define("sol.dev.install.functions.ReloadModules", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.InstallUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return !!me.config.install.reloads;
      },

      /**
       * Reloads modules
       */
      execute: function () {
        var me = this,
            exceptionString;

        if (me.config.install.reloads.ix) {
          if (me.config.base.scriptEnvironment == "SH") {
            me.handler.log({ text: "Skip IX reload: scriptEnvironment=" + me.config.base.scriptEnvironment });
          } else {
            me.handler.log({ text: "Reload ELOix...", statusBar: true });

            try {
              me.conn.ix().reload();
            } catch (ex) {
              exceptionString = sol.common.ExceptionUtils.parseException(ex);
              me.handler.log({ text: "Can't reload IX: exception=" + exceptionString });
            }
          }
        }

        if (me.config.install.reloads.wfForms) {
          me.sendHttpRequest("Reload ELOwf forms", "{{eloWfBaseUrl}}/wf/edit.jsp?reload=1&lang=en&ticket={{ticket}}");
        }

        if (me.config.install.reloads.apps) {
          me.sendHttpRequest("Reload ELOapps", "{{eloWfBaseUrl}}/apps/rest/cmd/refresh/", { method: "post" });
        }
      }
    });

    sol.define("sol.dev.install.functions.ExecuteRegisteredFunctions", {
      extend: "sol.dev.install.InstallFunctionBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;
        return !!me.config.install.executeRegisteredFunctions;
      },

      /**
       * Executes registered functions
       */
      execute: function () {
        var me = this,
            funcCalls, i, funcCall, jsonParams, jsonResult, connPropsString;

        if (me.config.install.executeRegisteredFunctions.reloadScripts) {
          me.handler.log({ text: "Reload IX scripts...", statusBar: true });
          connPropsString = me.getPropertiesString(me.conn.connProperties);
          me.handler.log({ text: "connProps = " + connPropsString });
          me.conn.ix().reloadScripts();
          me.handler.log({ text: "Scripts reloaded." });
        }

        me.handler.log({ text: "Execute registered functions...", statusBar: true });

        funcCalls = me.config.install.executeRegisteredFunctions.funcCalls;

        for (i = 0; i < funcCalls.length; i++) {
          funcCall = funcCalls[i];
          jsonParams = JSON.stringify(funcCall.params);

          me.handler.log({ text: "Execute registered function '{0}': params={1}", data: [funcCall.funcName, jsonParams] });

          jsonResult = me.conn.ix().executeRegisteredFunctionString(funcCall.funcName, jsonParams);

          me.handler.log({ text: "result={0}", data: [jsonResult] });
        }
      },

      // For GraalJS compatibility
      getPropertiesString: function (props) {
        var entryIterator,
            strArr = [],
            entry;

        if (!props) {
          return "";
        }

        entryIterator = props.entrySet().iterator();
        while (entryIterator.hasNext()) {
          entry = entryIterator.next();
          strArr.push(entry.key + " = " + entry.value);
        }

        return "{ " + strArr.join(", ") + " }";
      }
    });

    sol.define("sol.dev.install.functions.UninstallApps", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.InstallUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return !!me.config.install.uninstApps;
      },

      /**
       * Uninstalls ELOapps and ELOapps modules
       */
      execute: function () {
        var me = this;

        me.config.install.uninstApps.forEach(function (uninstObj) {
          me.sendHttpRequest("Uninstall ELOapp '" + uninstObj.appId + "'", "{{eloWfBaseUrl}}/apps/rest/cmd/app/undeploy/?namespace=" + uninstObj.namespace + "&appId=" + uninstObj.appId, { method: "post" });
        });
      }
    });

    sol.define("sol.dev.install.functions.InstallApps", {
      extend: "sol.dev.install.InstallFunctionBase",
      mixins: ["sol.dev.install.mixin.InstallUtils"],

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return !!me.config.install.instApps;
      },

      /**
       * Installs ELOapps and ELOapps modules
       */
      execute: function () {
        var me = this,
            modulesRootRepoPath, modulesRootFolderId, appsRootRepoPath, appsRootFolderId, moduleFolders, appFolders, manifest;

        modulesRootRepoPath = sol.common.RepoUtils.resolveSpecialFolder("{{bsFolderPath}}/" + me.config.install.packageName + "/ELOapps/Modules", me.config.base.repoPathParamObj);
        modulesRootFolderId = sol.common.RepoUtils.getObjId(modulesRootRepoPath);

        if (modulesRootFolderId) {
          moduleFolders = sol.common.RepoUtils.findChildren(modulesRootFolderId, { includeFolders: true, includeDocuments: false });
          moduleFolders.forEach(function (folder) {
            manifest = me.getManifestObj(folder.id);
            if (manifest) {
              me.sendHttpRequest("Install ELOapp module '" + manifest.id + "'", "{{eloWfBaseUrl}}/apps/rest/cmd/module/deploy/?moduleId=" + manifest.id, { method: "post" });
            } else {
              me.handler.log({ text: "Module manifest not found: {0}", data: [folder.name] });
            }
          });
        }

        appsRootRepoPath = sol.common.RepoUtils.resolveSpecialFolder("{{bsFolderPath}}/" + me.config.install.packageName + "/ELOapps/Apps", me.config.base.repoPathParamObj);
        appsRootFolderId = sol.common.RepoUtils.getObjId(appsRootRepoPath);

        if (appsRootFolderId) {
          appFolders = sol.common.RepoUtils.findChildren(appsRootFolderId, { includeFolders: true, includeDocuments: false });
          appFolders.forEach(function (folder) {
            manifest = me.getManifestObj(folder.id);
            if (manifest) {
              me.sendHttpRequest("Install ELOapp '" + manifest.id + "'", "{{eloWfBaseUrl}}/apps/rest/cmd/app/deploy/?namespace=" + manifest.namespace + "&appId=" + manifest.id, { method: "post", readTimeout: 30000 });
            } else {
              me.handler.log({ text: "App manifest not found: {0}", data: [folder.name] });
            }
          });
        }

        me.sendHttpRequest("Refresh icons", "{{eloWfBaseUrl}}/apps/rest/cmd/refreshicons/", { method: "post", readTimeout: 30000 });
      },

      /**
       * @private
       * Gets the content of the manifest file
       * @param {String} folderId
       * @return {String}
       */
      getManifestObj: function (folderId) {
        var me = this,
            content, manifestRepoPath, manifestObjId;

        manifestRepoPath = "ARCPATH[" + folderId + "]:/manifest";
        manifestObjId = sol.common.RepoUtils.getObjId(manifestRepoPath);
        if (!manifestObjId) {
          me.handler.log({ text: "Manifest file not found: " + manifestRepoPath });
          return;
        }
        content = sol.common.RepoUtils.downloadToString(manifestObjId) || "{}";
        return JSON.parse(content);
      }
    });

    sol.define("sol.dev.install.functions.ShiftDates", {
      extend: "sol.dev.install.InstallFunctionBase",

      initialize: function (config) {
        var me = this;

        me.$super("sol.dev.install.InstallFunctionBase", "initialize", [config]);
      },

      check: function () {
        var me = this;

        return !!me.config.install.shiftDates;
      },

      /**
       * Shift data for presentations
       */
      execute: function () {
        var me = this,
            manifestFilePath, refDateIso, shiftDates, i, shiftDateEntry, result;

        manifestFilePath = (new File(me.handler.payloadDirPath).parent) + java.io.File.separator + "MANIFEST.MF";

        if (!sol.common.FileUtils.exists(manifestFilePath)) {
          me.handler.log({ text: "Manifest file '{0} doesn't exist. Shift dates skipped", data: [manifestFilePath] });
          return;
        }

        refDateIso = me.getManifestCreatedDate(manifestFilePath);

        shiftDates = me.config.install.shiftDates;

        me.handler.log({ text: "Shift dates...", statusBar: true });

        for (i = 0; i < shiftDates.length; i++) {
          shiftDateEntry = shiftDates[i];
          shiftDateEntry.refDateIso = refDateIso;
          result = sol.dev.DateShiftUtils.shiftDates(shiftDateEntry);
          me.handler.log({ text: "Dates shifted: " + JSON.stringify(result) });
        }
      },

      getManifestCreatedDate: function (manifestFilePath) {
        var attributes, value, date, isoDate;

        if (!manifestFilePath) {
          throw "File path is empty";
        }
        attributes = sol.common.FileUtils.readManifestFile(manifestFilePath);
        value = attributes.getValue("Created-At");

        date = Packages.org.apache.commons.lang3.time.DateUtils.parseDate(value, ["yyyy-MM-dd'T'HH:mm:ssXXX"]);
        isoDate = Packages.org.apache.commons.lang3.time.DateFormatUtils.format(date, "yyyyMMddHHmmss") + "";

        return isoDate;
      }
    });

    sol.define("sol.dev.install.Installer", {
      singleton: true,

      functions: {},

      handlerClassNames: {
        JC: "sol.dev.install.JcHandler",
        AS: "sol.dev.install.AsHandler",
        IX: "sol.dev.install.IxHandler",
        SH: "sol.dev.install.ShHandler",
        TEST: "sol.dev.install.TestHandler"
      },

      initialize: function () {
        var me = this;

        me.register("install", "sol.dev.install.functions.CheckRunningSetup");
        me.register("install", "sol.dev.install.functions.CheckJcRequirements");
        me.register("install", "sol.dev.install.functions.CheckIxRequirements");
        me.register("install", "sol.dev.install.functions.CheckPermissions");
        me.register("install", "sol.dev.install.functions.CheckPackageAlreadyInstalled");

        me.register("install", "sol.dev.install.functions.CheckRepoPathsMustNotExist");

        me.register("install", "sol.dev.install.functions.CheckWfIsRunning");
        me.register("install", "sol.dev.install.functions.CheckAsLibs");
        me.register("install", "sol.dev.install.functions.CheckPackageDependencyInstalled");

        me.register("install", "sol.dev.install.functions.ProcessIssues");

        me.register("install", "sol.dev.install.functions.CreateUsers");
        me.register("install", "sol.dev.install.functions.CreateMasks");
        me.register("install", "sol.dev.install.functions.CheckDocMasks");
        me.register("install", "sol.dev.install.functions.AdjustDocMasks");

        me.register("install", "sol.dev.install.functions.UndeployPackage");
        me.register("install", "sol.dev.install.functions.UndeployHotfix");
        me.register("install", "sol.dev.install.functions.ReassignGuids");
        me.register("install", "sol.dev.install.functions.AddMemberships");
        me.register("install", "sol.dev.install.functions.RemoveMemberships");
        me.register("install", "sol.dev.install.functions.CreateColors");
        me.register("install", "sol.dev.install.functions.InstallWorkflowTemplateFiles");
        me.register("install", "sol.dev.install.functions.UninstallIxPlugins");
        me.register("install", "sol.dev.install.functions.InstallIxPlugins");
        me.register("install", "sol.dev.install.functions.UninstallApps");
        me.register("install", "sol.dev.install.functions.PrepareRepoPaths");
        me.register("install", "sol.dev.install.functions.ImportRepoData");
        me.register("install", "sol.dev.install.functions.AdjustDocMaskRights");
        me.register("install", "sol.dev.install.functions.CreateSordTypes");
        me.register("install", "sol.dev.install.functions.AssignSordTypes");
        me.register("install", "sol.dev.install.functions.DeleteObjects");
        me.register("install", "sol.dev.install.functions.UpdateSords");
        me.register("install", "sol.dev.install.functions.SetPreviewForms");
        me.register("install", "sol.dev.install.functions.SetProfileOpts");
        me.register("install", "sol.dev.install.functions.CreateChildrenReferences");
        me.register("install", "sol.dev.install.functions.CreateSingleReferences");
        me.register("install", "sol.dev.install.functions.ChangeRights");
        me.register("install", "sol.dev.install.functions.ConfigureAsUrl");
        me.register("install", "sol.dev.install.functions.ReloadModules");
        me.register("install", "sol.dev.install.functions.ExecuteRegisteredFunctions");
        me.register("install", "sol.dev.install.functions.InstallApps");
        me.register("install", "sol.dev.install.functions.ShiftDates");

        me.register("transport", "sol.dev.install.functions.CheckRunningSetup");
        me.register("transport", "sol.dev.install.functions.CheckJcRequirements");
        me.register("transport", "sol.dev.install.functions.CheckIxRequirements");
        me.register("transport", "sol.dev.install.functions.CheckPermissions");
        me.register("transport", "sol.dev.install.functions.TransportWarning");
        me.register("transport", "sol.dev.install.functions.CheckPackageAlreadyInstalled");

        me.register("transport", "sol.dev.install.functions.CheckWfIsRunning");
        me.register("transport", "sol.dev.install.functions.CheckAsLibs");
        me.register("transport", "sol.dev.install.functions.CheckPackageDependencyInstalled");

        me.register("transport", "sol.dev.install.functions.ProcessIssues");

        me.register("transport", "sol.dev.install.functions.CreateUsers");
        me.register("transport", "sol.dev.install.functions.CreateMasks");
        me.register("transport", "sol.dev.install.functions.UndeployPackage");
        me.register("transport", "sol.dev.install.functions.UndeployHotfix");
        me.register("transport", "sol.dev.install.functions.ReassignGuids");
        me.register("transport", "sol.dev.install.functions.AddMemberships");
        me.register("transport", "sol.dev.install.functions.CreateColors");
        me.register("transport", "sol.dev.install.functions.InstallWorkflowTemplateFiles");
        me.register("transport", "sol.dev.install.functions.UninstallIxPlugins");
        me.register("transport", "sol.dev.install.functions.InstallIxPlugins");
        me.register("transport", "sol.dev.install.functions.UninstallApps");
        me.register("transport", "sol.dev.install.functions.PrepareRepoPaths");
        me.register("transport", "sol.dev.install.functions.ImportRepoData");
        me.register("transport", "sol.dev.install.functions.AdjustDocMaskRights");
        me.register("transport", "sol.dev.install.functions.CreateSordTypes");
        me.register("transport", "sol.dev.install.functions.AssignSordTypes");
        me.register("transport", "sol.dev.install.functions.DeleteObjects");
        me.register("transport", "sol.dev.install.functions.UpdateSords");
        me.register("transport", "sol.dev.install.functions.SetPreviewForms");
        me.register("transport", "sol.dev.install.functions.SetProfileOpts");
        me.register("transport", "sol.dev.install.functions.CreateChildrenReferences");
        me.register("transport", "sol.dev.install.functions.CreateSingleReferences");
        me.register("transport", "sol.dev.install.functions.ConfigureAsUrl");
        me.register("transport", "sol.dev.install.functions.ReloadModules");
        me.register("transport", "sol.dev.install.functions.ExecuteRegisteredFunctions");
        me.register("transport", "sol.dev.install.functions.InstallApps");
        me.register("transport", "sol.dev.install.functions.ShiftDates");

        me.register("undeploy", "sol.dev.install.functions.UndeployPackage");

        me.register("deploy", "sol.dev.install.functions.DeployRenamePackage");
        me.register("deploy", "sol.dev.install.functions.CreateChildrenReferences");
        me.register("deploy", "sol.dev.install.functions.CreateSingleReferences");

        me.register("test", "sol.dev.install.functions.CheckDocMasks");
        me.register("test", "sol.dev.install.functions.AdjustDocMasks");
      },

      /**
       * Registers an installer function
       * @param {String} procName Installation procedure name
       * @param {String} funcName Function nameBlub
       */
      register: function (procName, funcName) {
        var me = this;

        if (!procName) {
          throw "Installation procedure name is empty";
        }

        if (!funcName) {
          throw "Function name is empty";
        }

        me.functions[procName] = me.functions[procName] || [];
        me.functions[procName].push(funcName);
      },

      /**
       * Call installer functions
       * @param {String} [procName=install] Installation procedure name
       * @param {Object} installConfig Installation configuration
       * @param {Object} result Output object
       * @param {Object} buildConfig Build configuration
       */
      execute: function (procName, installConfig, result, buildConfig) {
        var me = this,
            config, i, handlerClassName, procFunctions, funcName, func, javaVersion,
            scriptEnvironment, handler, connFact, timeoutSeconds,
            installFuncParams, timestamp, exceptionString;

        if (!installConfig) {
          throw "Configuration is empty";
        }

        buildConfig = buildConfig || {};

        if (installConfig.transport) {
          procName = "transport";
        }

        procName = procName || "install";

        result = result || {};
        result.issues = { items: [] };

        scriptEnvironment = (installConfig.test) ? "TEST" : sol.common.RepoUtils.detectScriptEnvironment();

        timestamp = sol.common.DateUtils.format(new Date(), "YYYY-MM-DD HH:mm");

        config = {
          install: installConfig,
          build: buildConfig,
          base: {
            scriptEnvironment: scriptEnvironment,
            repoPathParamObj: {
              packageName: installConfig.packageName,
              packageBaseFolderPath: installConfig.packageBaseFolderPath
            },
            timestamp: timestamp
          }
        };

        installFuncParams = {
          procName: procName,
          config: config,
          result: result
        };

        handlerClassName = me.handlerClassNames[scriptEnvironment];
        handler = sol.create(handlerClassName, installFuncParams);

        installFuncParams.handler = handler;

        procFunctions = me.functions[procName];
        if (!procFunctions) {
          throw "Installation procedure '" + procName + "' not found.";
        }

        javaVersion = sol.common.ExecUtils.getJavaVersion();

        handler.log({ text: "Start installation '{0}'", data: [config.install.setupName] });
        handler.log({ text: "Java version: {0}", data: [javaVersion] });
        handler.log({ text: "IX client library version: {0}", data: [ixConnect.clientVersion] });
        handler.log({ text: "mode={0}", data: [procName] });
        handler.log({ text: "scriptEnvironment={0}", data: [scriptEnvironment] });
        if (typeof Graal != "undefined") {
          handler.log({ text: "JavaScript engine: Graal: Graal.versionGraalVM={0}, Graal.isGraalRuntime={1}, Graal.language={2}, Graal.versionECMAScript={3}", data: [Graal.versionGraalVM || "", Graal.isGraalRuntime() || "", Graal.language || "", Graal.versionECMAScript || ""] });
        }

        if (scriptEnvironment != "IX") {

          timeoutSeconds = 600;

          handler.log({ text: "Create connection with different timeout: timoutSeconds={0}", data: [timeoutSeconds] });

          connFact = sol.common.RepoUtils.createConnFact(ixConnect.connProperties, ixConnect.sessionOptions, {
            timeoutSeconds: timeoutSeconds
          });
          installFuncParams.conn = connFact.createFromTicket(ixConnect.loginResult.clientInfo);
          installFuncParams.separateConn = true;
        } else {
          installFuncParams.conn = ixConnect;
          installFuncParams.separateConn = false;
        }

        for (i = 0; i < procFunctions.length; i++) {
          funcName = procFunctions[i];
          func = sol.create(funcName, installFuncParams);
          CURR_INST_FUNC = func;
          try {
            func.process();
          } catch (ex) {
            exceptionString = funcName + ": " + sol.common.ExceptionUtils.parseException(ex);
            handler.log({ text: "Exception: funcName={0}, exception={1}", data: [funcName, exceptionString] });
            if (globalScope && globalScope.runningSetup) {
              globalScope.runningSetup = false;
            }
            result.success = false;
            handler.finalize();
            throw exceptionString;
          }
          if (result.canceled) {
            break;
          }
          result.success = true;
        }

        if (installFuncParams.separateConn) {
          installFuncParams.conn.close();
          if (connFact) {
            connFact.done();
          }
        }

        handler.finalize();
      }
    });