    importPackage(Packages.de.elo.ix.client);

    //@include lib_Class.js
    //@include lib_sol.common.Cache.js
    //@include lib_sol.common.UserUtils.js
    //@include lib_sol.common.IxUtils.js
    //@include lib_sol.common.jc.ActionHandler.js

    /**
     * This class loads ELO Business Solutions action definitions and creates the ribbon, button groups and buttons.
     *
     * This class is automatically called during client startup.
     *
     * @author ELO Digital Office GmbH
     * @version 1.09.000
     *
     * @requires sol.common.IxUtils
     */
    sol.define("sol.common.jc.ActionDefinitionUtils", {
      singleton: true,

      TAB_CONST: {
        HOME: { name: "home" },
        DOCUMENT: { name: "document" },
        ARCHIVE: { name: "archive" },
        VIEW: { name: "view" },
        WORKFLOW: { name: "workflow" },
        INTRAY: { name: "intray" },
        SEARCH: { name: "search" },
        NEW: { name: "new" },
        OUTPUT: { name: "output" },
        ORGANIZE: { name: "organize" }
      },

      BUTTON_MODES: {
        BIG: { name: "big", jcPrio: 1 },
        SMALL: { name: "small", jcPrio: 3 }
      },

      initialize: function (config) {
        var me = this;
        me.$super("sol.Base", "initialize", [config]);
      },

      /**
       * Initializes ribbon buttons, groups and tabs for ELO Business Solutions.
       *
       * If called with a context, the method will use the old JavaClient way to initialize all the ribbon buttons.
       * If called without a context, the method will use the new dynamic button registration.
       *
       * @param {Object} ctx (optional) The current script scope.
       */
      initializeRibbon: function (ctx) {
        var me = this,
            result, errorMessage;

        me.logJavaInfo();

        try {
          result = sol.common.IxUtils.execute("RF_sol_common_services_ActionDefinitionCollector", {});

          if (result && result.definitions) {
            me.jcVersion20OrLater = sol.common.IxUtils.checkVersion(workspace.getClientVersion(), "20.00.000");
            me.buildRibbonFromDefinition(result.definitions, ctx);
          }
        } catch (ex) {
          errorMessage = String(ex);
          me.logger.error("loading ribbon definition failed.", errorMessage);
        }
      },

      /**
       * @private
       * Logs the Java version and the path of the JRE
       */
      logJavaInfo: function () {
        var me = this,
            urlLoaderClassName = "com.sun.webkit.network.URLLoader",
            javaRuntimeVersion, bitness, javaVersion, urlLoaderClass, urlLoaderClassLocation, urlLoaderJarDate;

        javaRuntimeVersion = String(java.lang.System.getProperty("java.runtime.version"));
        bitness = String(java.lang.System.getProperty("sun.arch.data.model"));
        javaVersion = javaRuntimeVersion + " - " + bitness + " bit";
        me.logger.info("java.version=" + javaVersion);
        me.logger.info("java.home=" + java.lang.System.getProperty("java.home"));
        me.logger.info("java.ext.dirs=" + java.lang.System.getProperty("java.ext.dirs"));
        try {
          urlLoaderClass = java.lang.Class.forName(urlLoaderClassName);
          urlLoaderClassLocation = urlLoaderClass.protectionDomain.codeSource.location;
          urlLoaderJarDate = new Date(new java.io.File(urlLoaderClassLocation.toURI()).lastModified());
          me.logger.info(["Location of the class '{0}': {1} (last modified: {2})", urlLoaderClassName, urlLoaderClassLocation, urlLoaderJarDate]);
        } catch (ignore) {
        }
      },

      /**
       * @private
       * Builds the java client ribbon based on ribbon button / group / tab definitions given by RF_sol_common_services_ActionDefinitionCollector.
       * @param {Object[]} defs action/ ribbon definition
       * @param {Object} ctx
       */
      buildRibbonFromDefinition: function (defs, ctx) {
        var me = this,
            dynRibbon = {
              _tabs: {},
              tabs: [],
              _bands: {},
              bands: [],
              _buttons: {},
              buttons: []
            },
            def, i, j, rib,
            tabIdentifierValid, bandIdentifierValid, buttonIdentifierValid;

        for (i = 0; i < defs.length; i++) {
          def = defs[i];

          // legacy action definition - just one ribbon definition instead of an array
          if (def.ribbon) {
            def.ribbons = [def.ribbon];
          }

          if (def.ribbons) {
            for (j = 0; j < def.ribbons.length; j++) {
              rib = def.ribbons[j];
              tabIdentifierValid = me.isValidId("rib.ribbonTab.name", rib.ribbonTab.name);

              if (rib && rib.button && rib.ribbonTab && rib.buttongroup && tabIdentifierValid) {
                if (!dynRibbon._tabs[rib.ribbonTab.name]) {
                  dynRibbon._tabs[rib.ribbonTab.name] = true;
                  dynRibbon.tabs.push({
                    name: rib.ribbonTab.name,
                    text: rib.ribbonTab.text,
                    position: rib.ribbonTab.position,
                    access: rib.ribbonTab.access
                  });
                }

                bandIdentifierValid = me.isValidId("rib.buttongroup.name", rib.buttongroup.name);
                if (!dynRibbon._bands[rib.buttongroup.name] && bandIdentifierValid) {
                  dynRibbon._bands[rib.buttongroup.name] = true;
                  dynRibbon.bands.push({
                    ribbonTab: rib.ribbonTab,
                    name: rib.buttongroup.name,
                    text: rib.buttongroup.text,
                    position: rib.buttongroup.position
                  });
                }

                buttonIdentifierValid = me.isValidId("rib.button.name", rib.button.name);
                if (buttonIdentifierValid) {
                  if (!dynRibbon._buttons[rib.button.name]) {
                    dynRibbon._buttons[rib.button.name] = {
                      type: def.type,
                      action: def.action,
                      button: rib.button,
                      additionalButtonPositions: [],
                      ribbonTab: rib.ribbonTab,
                      buttongroup: rib.buttongroup
                    };
                    dynRibbon.buttons.push(dynRibbon._buttons[rib.button.name]);
                  } else {
                    dynRibbon._buttons[rib.button.name].additionalButtonPositions.push({ ribbonTab: rib.ribbonTab, buttongroupName: rib.buttongroup.name });
                  }
                }
              }
            }
          }
        }

        me.registerTabs(dynRibbon.tabs, ctx);
        me.registerBands(dynRibbon.bands, ctx);
        me.registerButtons(dynRibbon.buttons, ctx);
      },

      /**
       * @private
       * Registers the tabs.
       * @param {Object[]} tabDefinitions This contains one action definitions per tab which was found
       * @param {Object} ctx (optional) Whether this is set or not, this method will use the old or the new way to register tabs in the client (see {@link #initializeRibbon})
       */
      registerTabs: function (tabDefinitions, ctx) {
        var me = this,
            tabsArray = [];

        if (ctx) {
          tabDefinitions.forEach(function (def) {
            if (!me.TAB_CONST[def.name]) {
              tabsArray.push(def.position + "," + def.text);
            }
          });

          ctx.getExtraTabs = function () {
            return tabsArray.join(";");
          };
        } else {
          tabDefinitions.forEach(function (def) {
            var tab;
            if (!me.TAB_CONST[def.name]) {
              tab = ribbon.addTab(def.position, null, def.name);
              tab.setTitle(def.text);
              if (def.access && (me.jcVersion20OrLater === true)) {
                tab.setVisibleCallback(function () {
                  return me.buttonEnabledHandler(def.access, def.name);
                }, me);
              }
            }
          });
        }
      },

      /**
       * @private
       * Registers the buton groups.
       * @param {Object[]} bandDefinitions This contains one action definitions per button group which was found
       * @param {Object} ctx (optional) Whether this is set or not, this method will use the old or the new way to register tabs in the client (see {@link #initializeRibbon})
       */
      registerBands: function (bandDefinitions, ctx) {
        var me = this,
            bandsArray = [];

        if (ctx) {
          bandDefinitions.forEach(function (def) {
            var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab;
            bandsArray.push(tabDef.name + "," + def.position + "," + def.text);
          });

          ctx.getExtraBands = function () {
            return bandsArray.join(";");
          };
        } else {
          bandDefinitions.forEach(function (def) {
            var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab;
            ribbon.addBand(tabDef.name, def.position, def.name).setTitle(def.text);
          });
        }
      },

      /**
       * @private
       * Registers the butons.
       * @param {Object[]} buttonDefinitions This contains one action definitions per button which was found
       * @param {Object} ctx (optional) Whether this is set or not, this method will use the old or the new way to register tabs in the client (see {@link #initializeRibbon})
       */
      registerButtons: function (buttonDefinitions, ctx) {
        var me = this;

        buttonDefinitions.sort(function (def1, def2) {
          return def1.button.position - def2.button.position;
        });

        if (ctx) {
          me.registerButtonsOld(buttonDefinitions, ctx);
        } else {
          me.registerButtonsNew(buttonDefinitions);
        }

      },

      /**
       * @private
       * Registers the butons in the old fashion.
       * @param {Object[]} buttonDefinitions This contains one action definitions per button group which was found
       * @param {Object} ctx
       */
      registerButtonsOld: function (buttonDefinitions, ctx) {
        var me = this,
            buttonPositions = [];
        buttonDefinitions.forEach(function (def) {
          var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab;

          buttonPositions.push(def.button.jc.buttonId + "," + tabDef.name + "," + def.buttongroup.text + "," + me.getPriority(def));

          me.buttonEnabledStatic(def.button.jc.buttonId, def.button.access);

          ctx["eloScriptButton" + def.button.jc.buttonId + "Start"] = function () {
            me.buttonHandler(def);
          };

          ctx["getScriptButton" + def.button.jc.buttonId + "Name"] = function () {
            return def.button.text;
          };

          ctx["getScriptButton" + def.button.jc.buttonId + "Tooltip"] = function () {
            return def.button.tooltipText;
          };

        });

        ctx.getScriptButtonPositions = function () {
          return buttonPositions.join(";");
        };
      },

      /**
       * @private
       * Registers the butons in the new dynamic way.
       * @param {Object[]} buttonDefinitions This contains one action definitions per button group which was found
       */
      registerButtonsNew: function (buttonDefinitions) {
        var me = this,
            abp;

        buttonDefinitions.forEach(function (def) {
          var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab,
              btn = ribbon.addButton(tabDef.name, def.buttongroup.name, def.button.name),
              imageName = me.getIconName(def.button), i;

          if (def.additionalButtonPositions && def.additionalButtonPositions.length > 0) {
            if (typeof btn.addPosition == "function") {
              for (i = 0; i < def.additionalButtonPositions.length; i++) {
                abp = def.additionalButtonPositions[i];
                tabDef = me.TAB_CONST[abp.ribbonTab.name] || abp.ribbonTab;
                btn.addPosition(tabDef.name, abp.buttongroupName);
              }
            } else {
              me.logger.error("ActionDef Button: " + def.button.name + " - additionalButtonPositions found. This feature is not supported by this client. Please update to a newer version.");
            }
          }

          btn.setCallback(function () {
            me.buttonHandler(def);
          }, me);
          btn.setTitle(def.button.text);
          btn.setTooltip(def.button.tooltipText);
          btn.setIconName(imageName);
          btn.asTile(def.button.asTile === true);
          btn.setTileIconName(imageName);
          btn.setPriority(me.getPriority(def));

          if (def.button.access && def.button.access.solTypes) {
            btn.setEnabledCallback(function () {
              return me.buttonEnabledHandler(def.button.access, def.button.name);
            }, me);
          } else {
            me.buttonEnabledStatic(btn.fctNr - 9000, def.button.access);
          }

          if (btn.setDefaultPinState) {
            btn.setDefaultPinState(!!def.button.pinned);
          }
        });
      },

      /**
       * @private
       * @param {String} idType Identifier type
       * @param {String} id Identifier
       * @return {Boolean} is valid
       */
      isValidId: function (idType, id) {
        var me = this,
            valid = true,
            idRegEx;

        idType = (idType || "ID") + "";
        id += "";

        idRegEx = "^[0-9a-zA-Z_-]*$";

        if (!id) {
          me.logger.warn("ID is missing: idType=" + idType);
          valid = false;
        } else if (!id.match(idRegEx)) {
          me.logger.warn("ID contains illegal characters: idType=" + idType + ", id=" + id + ", idRegEx=" + idRegEx);
          valid = false;
        }

        return valid;
      },

      /**
       * @private
       * @param {Object} buttonDef
       * @return {String}
       */
      getIconName: function (buttonDef) {
        var me = this;
        if (buttonDef && buttonDef.iconName && (me.jcVersion20OrLater === true)) {
          return buttonDef.iconName;
        }
        return "ScriptButton" + buttonDef.jc.buttonId;
      },

      /**
       * @private
       * Retrieves the priority from a button definition.
       * @param {Object} btnDef
       * @return {Number}
       */
      getPriority: function (btnDef) {
        var me = this;
        return (btnDef.buttongroup.mode === me.BUTTON_MODES.SMALL.name) ? me.BUTTON_MODES.SMALL.jcPrio : me.BUTTON_MODES.BIG.jcPrio;
      },

      /**
       * @private
       * Checks, when the button should be enabled, and calls the `setScriptButtonEnabled` method accordingly.
       * This is used by the old way of registering buttons and only while the button is initialized.
       * @param {String} btnId A button id
       * @param {Object} access A button access definition
       */
      buttonEnabledStatic: function (btnId, access) {
        if (access && (access.document || access.folder || access.multi)) {
          workspace.setScriptButtonEnabled(
            btnId,
            access.document === true,
            access.folder === true,
            false,
            false,
            false,
            access.multi === true
          );
        }
      },

      /**
       * @private
       * Checks, if the button should be enabled. This is used by the the new way of registering buttons and will be called dynamically.
       * @param {Object} access The access element of a button definition
       * @return {Boolean}
       */
      buttonEnabledHandler: function (access, targetName) {
        var me = this,
            activeView, activeNavBarSelection, checkElement, selectionCount, firstSelected, allSelected, current, i;

        // if there is no access definition (or it has no content) the element should always be enabled
        if (!access || (!access.document && !access.folder && !access.multi && !access.solTypes)) { // nothing set, button will always be enabled
          return true;
        }

        try {
          activeView = workspace.activeView;
        } catch (ex) {
          me.logger.warn("Active view not available: " + ex);
          return false;
        }

        activeNavBarSelection = workspace.workspace.navigationBar && workspace.workspace.navigationBar.active && workspace.workspace.navigationBar.active.selectedIds;

        // function to check a single indexed element
        checkElement = function (e) {
          var solType, fieldValue, conditions, visible = false;

          if (!e) {
            return false;
          }

          if ((access.document !== true || access.folder !== true) && ((access.document === true && !e.isDocument()) || (access.folder === true && !e.isStructure()))) {
            return false;
          }

          if (!access.solTypes || (access.solTypes.length <= 0)) {
            return true;
          }

          try {
            solType = e.getObjKeyValue("SOL_TYPE") + "";

            if (solType != "" && access.solTypes.indexOf(solType) !== -1) {
              conditions = access.conditions;
              if (conditions && Array.isArray(conditions)) {
                // we're checking if one of our filter criterias is not
                // true. This is when our some-function returns true
                // (and we're inverting this for the 'visible' variable)
                visible = !conditions.some(function (condition) {
                  if (condition.key && condition.value) {
                    try {
                      fieldValue = e.getObjKeyValue(condition.key) + "";
                      return fieldValue != condition.value;
                    } catch (ex) {
                      me.logger.warn(["Button/Tab access check failed. Condition Field {0} does not exist in mask of clicked object {1}", condition.key, e.name], targetName);
                      return true;
                    }
                  } else {
                    me.logger.error("Button/Tab access check failed. Bad condition configuration.", targetName);
                    return true;
                  }
                });
              } else {
                visible = true;
              }
            }
          } catch (ex) {
            // can not get value from the SOL_TYPE field
            visible = false;
          }
          return visible;
        };

        selectionCount = (activeView) ? activeView.selectionCount : ((activeNavBarSelection) ? activeNavBarSelection.length : 0);

        if (selectionCount <= 0) {
          return false; // nothing selected
        }

        // check single access
        if (access.multi !== true) {
          if (selectionCount !== 1) {
            return false;
          }
          firstSelected = (activeView) ? activeView.firstSelected : ((activeNavBarSelection) ? archive.getElement(workspace.workspace.navigationBar.active.selectedIds[0]) : null);
          return checkElement(firstSelected);
        }

        // check multi select
        if (activeView && activeView.allSelected) { // for normal views
          allSelected = activeView.allSelected;
          while (allSelected.hasMoreElements()) {
            current = allSelected.nextElement();
            if (!checkElement(current)) {
              return false;
            }
          }
        } else if (activeNavBarSelection) { // for AppViews, because those do not have an active view in 9.03
          allSelected = workspace.workspace.navigationBar.active.selectedIds; // do not try this at home
          for (i = 0; i < allSelected.length; i++) {
            current = allSelected[i];
            if (!checkElement(archive.getElement(current))) {
              return false;
            }
          }
        } else {
          return false;
        }

        return true;
      },

      /**
       * This handles the button execution. This is used by the old and the new way of registering buttons alike.
       * @param {Object} def A button definition
       */
      buttonHandler: function (def) {
        var me = this,
            objId = me.getSelection(def.button.access);

        switch (def.type) {
          case "ADVANCED_ACTION":
            sol.common.jc.ActionHelper.executeAdvancedAction(objId, def.action);
            break;
          case "SIMPLE_ACTION":
            sol.common.jc.ActionHelper.executeSimpleAction(objId, def.action);
            break;
          default:
            me.logger.error("No handler found for button definition", def.button.name);
        }
      },

      /**
       * @private
       * returns the selected element objIds.
       * @param {Object} access This object determines if this returns just one or many objIds (in case of multiselect).
       * @return {String|String[]}
       */
      getSelection: function (access) {
        var allSelected, selected, objIds;

        if (workspace.activeView && workspace.activeView.allSelectedArchiveElements) {
          allSelected = workspace.activeView.allSelectedArchiveElements;
          objIds = [];
          while (allSelected.hasMoreElements()) {
            selected = allSelected.nextElement();
            if (selected.id) {
              objIds.push(selected.id);
            }
          }
        } else if (workspace.workspace.navigationBar && workspace.workspace.navigationBar.active && workspace.workspace.navigationBar.active.selectedIds) {

          // FIXME as soon as the JC supports the retrival of selected elements from "AppViews" this should use the official API

          allSelected = workspace.workspace.navigationBar.active.selectedIds; // do not try this at home
          objIds = allSelected.map(function (objId) {
            return objId;
          }); // convert to JavaScript array
        }

        if (access && (access.multi === true)) {
          return (objIds && objIds.length > 0) ? objIds : null;
        } else {
          return (objIds && objIds.length > 0) ? objIds[0] : null;
        }
      }

    });

    // This will be used by ELO10 JavaClients //
    function eloExpandRibbon() {
      if (typeof ribbon !== "undefined") {
        sol.common.jc.ActionDefinitionUtils.initializeRibbon();
      }
    }

    // This is the fallback for older JavaClients //
    (function (ctx) {
      if (typeof ribbon === "undefined") {
        sol.common.jc.ActionDefinitionUtils.initializeRibbon(ctx);
      }
    }(this));