//@include lib_Class.js
//@include lib_sol.common.StringUtils.js
//@include lib_sol.common.ObjectUtils.js
//@include lib_sol.common.JsonUtils.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.IxUtils.js
//@include lib_sol.common.CounterUtils.js
//@include lib_sol.common.AclUtils.js
//@include lib_sol.common.TranslateTerms.js
//@include lib_sol.common.Template.js
//@include lib_sol.common.WfUtils.js
//@include lib_sol.common.Map.js
//@include lib_sol.common.ix.RfUtils.js

/**
 * @abstract
 *
 * Base class for actions.
 *
 * Actions allow providing the same functionality in several clients. The primary goal is to handle core operations
 * (creating a folder and starting a workflow) in the process function of an action. Further logic should be implemented
 * by workflow function modules.
 *
 * Subclasses have to implement the two abstract methods 'getName' and 'process'. These are used by the 'execute' function.
 *
 * # Response
 *
 * Actions can return various types in form of a JSON String. The response is a standardized format and handled by this class.
 * There are three return types:
 *
 * - events
 * - messages (deprecated, because not used in standard action handling)
 * - arbitrary data (deprecated, because not used in standard action handling)
 *
 * ## Add event functions
 *
 * Events are used in order to define a postprocessing in the clients. This allows to trigger a goto to the created element in elo or display a dialog that contains a workflow form.
 *
 * Following example shows how to display a dialog by a given flow id.
 *
 *     me.addWfDialogEvent(flowId, { objId: objId });
 *
 * Currently following events are supported:
 *
 * - addWfDialogEvent
 * - addUrlDialogEvent
 * - addAppDialogEvent (utilizes url event)
 * - addActionEvent
 * - addRefreshEvent
 * - addGotoIdEvent
 * - addGotoWfTaskEvent
 * - addErrorEvent
 * - addInfoEvent
 * - addFeedbackEvent
 *
 * Please note that events are handled synchronously. If addWfDialogEvent and addRefreshEvent is called. The client will refresh the element after the users closes the dialog.
 *
 * # onSuccess and onFailure
 * These functions can be implemented by the subclass. They will be called either if the action was processed successfully or if there occured an error.
 *
 * # Conditional events
 *
 * It is possible to declare events as conditional. The client handler will evaluate if the event should be executed.
 * This is required since workflow processing can be influenced by the user. As an example: A WF-Form is displayed in a dialog. If the user clicks cancel refresh and goto events should'nt be excecuted.
 *
 * The events has to declare an `ON` property, which has to define the following properties.
 *
 * - type {String}: "WF_STATUS"|"SORD"|"GRP"|"MAP"
 * - key {String}: only for type="SORD"|"GRP"|"MAP", contains the sord property, the group key or map key
 * - objId {String}: only for type="SORD"|"GRP"|"MAP", the objId of the sord which should be checked
 * - flowId {String}: only for type="WF_STATUS", the workflow id to get the status from
 * - value {String}: the value which has to be fulfilled
 *
 * Following example shows a condition used by the refresh event.
 *
 *     me.addRefreshEvent(objId, {
 *       type: "WF_STATUS",
 *       value: "APPROVE",
 *       flowId: flowId
 *     });
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.05.000
 *
 * @eloix
 * @eloas
 * @requires sol.common.StringUtils
 * @requires sol.common.ObjectUtils
 * @requires sol.common.JsonUtils
 * @requires sol.common.SordUtils
 * @requires sol.common.IxUtils
 * @requires sol.common.CounterUtils
 * @requires sol.common.AclUtils
 * @requires sol.common.TranslateTerms
 * @requires sol.common.WfUtils
 * @requires sol.common.WfMap
 */
sol.define("sol.common.ActionBase", {

  _registeredEvents: undefined,

  _messages: undefined,

  _data: undefined,

  /**
   * @private
   * @property {String} _APP_URL_TEMPLATE Template for the app event
   */
  _APP_URL_TEMPLATE: "{{eloWfBaseUrl}}/apps/app/{{appName}}/?lang={{language}}&ticket={{ticket}}",

  /**
   * @private
   * @property {String} _APP_URL_TEMPLATE_12 Template for the app event since ELO12
   */
  _APP_URL_TEMPLATE_12: "{{eloWfBaseUrl}}/apps/app/{{appName}}/?lang={{language}}",

  actionId: undefined,

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

    if (me.$className === "sol.common.ActionBase") {
      throw "can not create instance of abstract class 'sol.common.ActionBase'";
    }
    RhinoManager.registerClass(me.$className);
    me.$super("sol.Base", "initialize", [config]);
    me._registeredEvents = [];
    me._messages = [];
    me._data = {};
    me.actionId = sol.common.CounterUtils.incCounter("SOL_ACTION_ID");
  },

  /**
   * @abstract
   * Name of the action. Has to be implemented by subclass.
   */
  getName: function () {
    throw "cannot call 'name' of abstract class 'sol.common.ActionBase'";
  },

  /**
   * @abstract
   * Implementation of the action. Has to be implemented by subclass.
   */
  process: function () {
    throw "cannot call 'process' of abstract class 'sol.common.ActionBase'";
  },

  /**
   * Will be called in case of a successfull action execution. The implementation in the subclass is optional.
   */
  onSuccess: function () { },

  /**
   * Will be called in case of an action failure. The implementation in the subclass is optional.
   * The default implementation will write an ERROR log entry and call {@link #addErrorEvent}.
   * @param {Exception} exception
   */
  onFailure: function (exception) {
    var me = this;
    me.logger.error(["Error processing '{0}'", me.$className], exception);
    me.addErrorEvent(exception.message || exception);
  },

  /**
   * @private
   * Marks an action (aka the workflow) as creation, or not.
   *
   * An action workflow will be marked as creation if there is a registered event with a `flowId` and either:
   *
   *  - the action defines a `$new: true` property
   *  - or it is a `StandardAction` with a `$new` parameter.
   *  - or the action name of the action start with 'create' (and `$new` is not set to `false` explicitly)
   *
   */
  markCreationAction: function () {
    var me = this,
        isNew, flowId, objId, wfmap;

    isNew = function () {
      var reason;
      if (me.$new === true) {
        reason = "Action defines '$new=true'";
      } else if (me.$className === "sol.common.ix.actions.Standard" && me.$new) {
        reason = "StandardAction with '$new' property";
      } else if ((me.getName().indexOf("Create") === 0) && (me.$new !== false)) {
        reason = "Action name starts with 'Create' and '$new=" + me.$new + "'";
      }
      if (reason) {
        me.logger.info("Mark as 'created' to suppress update registration in form. Reason: " + reason);
      }
      return !!reason;
    };

    me._registeredEvents.some(function (event) {
      if (event.obj && event.obj.flowId) {
        flowId = event.obj.flowId;
        return flowId;
      }
    });

    if (flowId && isNew()) {
      objId = sol.common.WfUtils.getWorkflow(flowId).objId;
      wfmap = sol.create("sol.common.WfMap", {
        flowId: flowId,
        objId: objId
      });
      wfmap.setValue("COMMON_SKIP_UPDATE_REGISTRATION", "true");
      wfmap.write();
    }
  },

  /**
   * Handles the execution of Actions and internally calls sol.common.ix.ActionHandler#process.
   * This function is called by sol.common.jc.ActionHandler.execute
   * @return {Object}
   */
  execute: function () {
    var me = this;

    me.logger.enter("execute_" + me.$className);
    me.logger.info(["executing action '{0}' => actionId={1}", me.getName(), me.actionId]);

    try {
      me.process();
      me.markCreationAction();
      me.onSuccess();
    } catch (ex) {
      me.onFailure(ex);
    }

    me.logger.exit("execute_" + me.$className);

    return me.buildResponse();
  },

  stringifyAll: function (obj) {
    return JSON.stringify(obj, function (key, value) {
      if (value instanceof java.lang.String) {
        value = String(value);
      }
      if (value && value.getClass) {
        value = String(value.toString());
      }
      return value;
    });
  },

  renderConfig: function (config) {
    var me = this, str;
    str = Handlebars.compile(me.stringifyAll(config))({
      objId: config.objId,
      type: (config.$templating ? config.$templating.$type : config.type),
      tree: (config.$templating ? config.$templating.$tree : config.tree),
      preconditions: (config.$templating ? config.$templating.$preconditions : config.preconditions)
    });
    str = str.replace(/"/g, "\\"");
    return JSON.parse(org.apache.commons.lang.StringEscapeUtils.unescapeHtml(str));
  },

  /**
   * Checks if a user has the required rights on an object.
   *
   * If some rights are missing this will throw an exception.
   *
   * @param {String|de.elo.ix.client.Sord} sord ObjId or sord to check the access rights on
   * @param {Object} params
   * @param {String|Object} params.rights Either a string (in form 'RWDEL' or 'RWDELP' since ELO12) or an object (see {@link sol.common.AclUtils rights}) specifying the rights the user requires
   * @param {String} params.message (optional) This can specify a message to override the default exception text from `sol.common.ix.actions.errorRequiredUserRights`. Has priority over `messageKey`.
   * @param {String} params.messageKey (optional) This can specify a translation key to override the default exception text from `sol.common.ix.actions.errorRequiredUserRights`
   * @param {String|de.elo.ix.client.ClientInfo} params.language (optional)
   * Either an ISO language String, or an de.elo.ix.client.ClientInfo Object. Default will be the ClientInfo from the connection. Not relevant for `message`.
   * @throws Throws an exception if the user has not the specified rights
   */
  requireUserRights: function (sord, params) {
    var me = this,
        hasRights = true,
        cfg, exception;

    if (params && params.rights) {
      cfg = {};
      if (!sol.common.ObjectUtils.isObject(params.rights)) {
        cfg.rights = {};
        if (sol.common.StringUtils.contains(params.rights, "R")) {
          cfg.rights.r = true;
        }
        if (sol.common.StringUtils.contains(params.rights, "W")) {
          cfg.rights.w = true;
        }
        if (sol.common.StringUtils.contains(params.rights, "D")) {
          cfg.rights.d = true;
        }
        if (sol.common.StringUtils.contains(params.rights, "E")) {
          cfg.rights.e = true;
        }
        if (sol.common.StringUtils.contains(params.rights, "L")) {
          cfg.rights.l = true;
        }
        if (sol.common.StringUtils.contains(params.rights, "P")) {
          cfg.rights.p = true;
        }
      } else {
        cfg.rights = params.rights;
      }
      hasRights = sol.common.AclUtils.hasEffectiveRights(sord, cfg);
    }

    if (!hasRights) {
      if (params && params.message) {
        exception = params.message;
      } else {
        exception = me.getLocalizedString(
          (params && params.language) ? params.language : ixConnect.loginResult.clientInfo,
          (params && params.messageKey) ? params.messageKey : "sol.common.ix.actions.errorRequiredUserRights"
        );
      }
      throw exception;
    }
  },

  /**
   * @deprecated 1.03.000 Not used for standard action handling and might be removed in future versions
   * Add a (localized) massage to the response.
   * @param {String|de.elo.ix.client.ClientInfo} language Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
   * @param {String} msg Either a message string or a message key (if language is set)
   */
  addMessage: function (language, msg) {
    var me = this;

    if (language) {
      msg = this.getLocalizedString(language, msg);
    }

    me._messages.push(msg);
  },

  /**
   * @deprecated 1.03.000 Not used for standard action handling and might be removed in future versions
   * Add some payload data to the response
   * @param {String} key
   * @param {String} value
   */
  addPayload: function (key, value) {
    var me = this;
    me._data[key] = value;
  },

  /**
   * Add an event which tells the client to open a workflow form in a dialog.
   *
   * Please not that the flowId must be given in order to identify the current Workflow node for the user.
   *
   *     flowId = me.startWorkflow(contractObjId, contractConfig.workflows.approveContract.workflowTemplateName, wfName);
   *     me.addWfDialogEvent(flowId, { objId: objId });
   *
   * @param {Number} flowId (required)
   * @param {Object} params
   * @param {String} params.objId Either `nodeId` or `objId` is required
   * @param {Number} params.nodeId Either `nodeId` or `objId` is required
   * @param {String} params.title (optional) Dialog title
   * @param {String} params.dialogId (optional) Id so the client can save the size for the dialog
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addWfDialogEvent: function (flowId, params, on) {
    var me = this,
        eventdata = { flowId: flowId },
        wfCollectNode;

    if (params && params.nodeId) {
      eventdata.nodeId = params.nodeId;
    } else if (params && params.objId) {
      wfCollectNode = sol.common.WfUtils.findFirstActiveNode(params.objId, flowId);
      eventdata.flowId = wfCollectNode ? wfCollectNode.flowId : flowId; // could be the subworkflow ID
      eventdata.nodeId = wfCollectNode ? wfCollectNode.nodeId : null;
      eventdata.formSpec = (wfCollectNode && wfCollectNode.formSpec) ? wfCollectNode.formSpec : null;
    }

    if (!eventdata.flowId || !eventdata.nodeId) {
      return;
    }

    me.addDialogEvent(eventdata, params, on);
  },

  /**
   * Add an event which tells the client to open a URL in a dialog.
   *
   *     me.addUrlDialogEvent("http://server/myCustomApp");
   *
   * @param {String} url
   * @param {Object} params (optional)
   * @param {String} params.title (optional) Dialog title
   * @param {String} params.dialogId (optional) Id so the client can save the size for the dialog
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addUrlDialogEvent: function (url, params, on) {
    var me = this,
        eventdata = { url: url };

    me.addDialogEvent(eventdata, params, on);
  },

  /**
   * Add an event which tells the client to open a dialog with an app.
   *
   *     me.addAppDialogEvent();
   *
   * @param {String} appName The name of the app
   * @param {Object} params (optional)
   * @param {String} params.language (optional) Language shurtcut to call the app with. Default will be from IX connection.
   * @param {String} params.title (optional) Dialog title
   * @param {String} params.dialogId (optional) Id so the client can save the size for the dialog
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addAppDialogEvent: function (appName, params, on) {
    var me = this,
        language, eloWfBaseUrl, urlTemplate, url, eventdata;

    language = (params && params.language) ? params.language : ixConnect.loginResult.clientInfo.language;
    eloWfBaseUrl = sol.common.WfUtils.getWfBaseUrl();

    urlTemplate = (sol.common.IxUtils.checkVersion(ixConnect.implVersion, "12.00.000.000")) ? me._APP_URL_TEMPLATE_12 : me._APP_URL_TEMPLATE;

    url = sol.create("sol.common.Template", { source: urlTemplate }).apply({
      eloWfBaseUrl: eloWfBaseUrl,
      appName: appName,
      language: language,
      ticket: ixConnect.loginResult.clientInfo.ticket
    });

    if (!url) {
      return;
    }

    eventdata = { url: url };

    me.addDialogEvent(eventdata, params, on);
  },

  /**
   * @private
   * Adds a dialog event. Used by {@link #addWfDialogEvent}, {@link #addUrlDialogEvent} and {@link #addAppDialogEvent}
   * @param {Object} eventdata Prefilled data object (e.g. `url` or `flowId` and `nodeId`)
   * @param {Object} params (optional)
   * @param {String} params.title (optional) Dialog title
   * @param {String} params.dialogId (optional) Id so the client can save the size for the dialog
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addDialogEvent: function (eventdata, params, on) {
    var me = this,
        event;

    if (params && params.title) {
      eventdata.title = eventdata.title || params.title;
    }

    if (params && params.dialogId) {
      eventdata.dialogId = eventdata.dialogId || params.dialogId;
    }

    event = me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.DIALOG, eventdata, on);

    me._registeredEvents.push(event);
  },

  /**
   * Adds an event which tells the client to start another action.
   *
   * @param {String} registeredFunction RF of the action which should be called next
   * @param {Object} params Params for the RF which should be called next
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addActionEvent: function () {
    throw "cannot call 'addActionEvent' of class 'sol.common.ActionBase', function has to be implemented by subclass";
  },

  /**
   * Adds an event which tells the client to refresh the current view.
   *
   *     me.addRefreshEvent(objId);
   *
   * @param {String} objId
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addRefreshEvent: function (objId, on) {
    var me = this;
    me._registeredEvents.push(me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.REFRESH, { objId: objId }, on));
  },

  /**
   * Adds an event which tells the client to navigate to a given Sord id.
   *
   * Goto a new element.
   *
   *     me.addGotoIdEvent(objId);
   *
   * Goto a new element if the workflow status was set to "CREATE".
   *
   *     me.addGotoIdEvent(objId, undefined, {
   *       type: "WF_STATUS",
   *       value: "CREATE",
   *       flowId: flowId
   *     });
   *
   * Goto a new document and open the document for edit. (Checkout)
   *
   *     me.addGotoIdEvent(objId, true, {
   *       type: "WF_STATUS",
   *       value: "CREATE",
   *       flowId: flowId
   *     });
   *
   * @param {String} objId
   * @param {Boolean} checkout
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addGotoIdEvent: function (objId, checkout, on) {
    var me = this,
        eventCfg = { objId: objId };

    eventCfg.checkout = (checkout === true) ? true : false;

    me._registeredEvents.push(me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.GOTO, eventCfg, on));
  },

  /**
   * Adds an event which tells the client to navigate to a given task.
   *
   *     me.addGotoWfTaskEvent(flowId);
   *
   * @param {String} flowId (required)
   * @param {Object} params
   * @param {String} params.objId Either `nodeId` or `objId` is required
   * @param {String} params.nodeId Either `nodeId` or `objId` is required
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addGotoWfTaskEvent: function (flowId, params, on) {
    var me = this,
        eventdata = { flowId: flowId },
        node;

    if (params && params.nodeId) {
      eventdata.nodeId = params.nodeId;
    } else if (params && params.objId) {
      node = sol.common.WfUtils.findFirstActiveNode(params.objId, flowId);
      eventdata.nodeId = (node) ? node.nodeId : null;
    }

    if (!eventdata.flowId || !eventdata.nodeId) {
      return;
    }

    me._registeredEvents.push(me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.GOTO, eventdata, on));
  },

  /**
   * Adds an error event.
   *
   *     if (!me.templateId) {
   *       me.addErrorEvent("partner.msgs.errormessage", null, null, me.ci);
   *       return;
   *     }
   *
   * @param {String} message Either a message string or a message key (if language is set)
   * @param {String} errorcode (optional)
   * @param {String} exception (optional)
   * @param {String|de.elo.ix.client.ClientInfo} language (optional) Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addErrorEvent: function (message, errorcode, exception, language, on) {
    var me = this,
        errorCfg = {},
        errorEvent;

    if (language) {
      message = me.getLocalizedString(language, message);
    }

    if (message) {
      errorCfg.msg = message;
    }
    if (errorcode) {
      errorCfg.code = errorcode;
    }
    if (exception) {
      errorCfg.ex = exception;
    }

    errorEvent = me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.ERROR, errorCfg, on);

    me._registeredEvents.push(errorEvent);
  },

  /**
   * Adds an info event.
   *
   *     addFeedbackEvent("sol.pubsec.MyString", "de")
   *
   * will return the string as defined the relevant properties language file of the indexserver translation
   *
   *     addFeedbackEvent("hello {{str1}}", null, { str1: "world" })  ==> "hello world"
   *
   * Assuming, that we have property language keys defined for German (`sol.pubsec.MyString=Herr {{name}} wir grüßen Sie`) and English (`sol.pubsec.MyString=Greetings Mr. {{name}}`):
   *
   *     addFeedbackEvent("sol.pubsec.MyString", "de", { name: "Mustermann" })  ==> "Herr Mustermann wir grüßen Sie"
   *     addFeedbackEvent("sol.pubsec.MyString", "en", { name: "Mustermann" })  ==> "Greetings Mr. Mustermann"
   *
   * @param {String} message Either a message string or a message key (if language is set)
   * @param {String|de.elo.ix.client.ClientInfo} language (optional) Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
   * @param {Object} params (optional) if set, the message will be used as a handlebars string with the params applied (if there is also a `language`, the translation will be applied first)
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addInfoEvent: function (message, language, params, on) {
    var me = this,
        feedbackCfg, feedbackEvent;

    feedbackCfg = me.createFeedbackEventCfg(message, language, params, on);
    if (feedbackCfg) {
      feedbackCfg.permanent = true;
      feedbackEvent = me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.FEEDBACK, feedbackCfg, on);
      me._registeredEvents.push(feedbackEvent);
    }
  },

  /**
   * Adds a feedback event.
   *
   *     addFeedbackEvent("sol.pubsec.MyString", "de")
   *
   * will return the string as defined the relevant properties language file of the indexserver translation
   *
   *     addFeedbackEvent("hello {{str1}}", null, { str1: "world" })  ==> "hello world"
   *
   * Assuming, that we have property language keys defined for German (`sol.pubsec.MyString=Herr {{name}} wir grüßen Sie`) and English (`sol.pubsec.MyString=Greetings Mr. {{name}}`):
   *
   *     addFeedbackEvent("sol.pubsec.MyString", "de", { name: "Mustermann" })  ==> "Herr Mustermann wir grüßen Sie"
   *     addFeedbackEvent("sol.pubsec.MyString", "en", { name: "Mustermann" })  ==> "Greetings Mr. Mustermann"
   *
   * @param {String} message Either a message string or a message key (if language is set)
   * @param {String|de.elo.ix.client.ClientInfo} language (optional) Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
   * @param {Object} params (optional) if set, the message will be used as a handlebars string with the params applied (if there is also a `language`, the translation will be applied first)
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  addFeedbackEvent: function (message, language, params, on) {
    var me = this,
        feedbackCfg, feedbackEvent;

    feedbackCfg = me.createFeedbackEventCfg(message, language, params, on);
    if (feedbackCfg) {
      feedbackCfg.permanent = false;
      feedbackEvent = me.createEvent(sol.common.IxUtils.CONST.EVENT_TYPES.FEEDBACK, feedbackCfg, on);
      me._registeredEvents.push(feedbackEvent);
    }
  },

  /**
   * @private
   * Creates an basic feedback event config. Used by {@link #addFeedbackEvent} and {@link addInfoEvent}.
   * @param {String} message
   * @param {String|de.elo.ix.client.ClientInfo} language
   * @param {Object} params
   * @param {Object} on
   * @return {Object}
   */
  createFeedbackEventCfg: function (message, language, params, on) {
    var me = this,
        feedbackCfg = {};

    if (!message) {
      return null;
    }
    if (language) {
      message = me.getLocalizedString(language, message);
    }

    if (params) {
      message = sol.create("sol.common.Template", { source: message }).apply(params);
    }

    feedbackCfg.msg = message;

    return feedbackCfg;
  },

  /**
   * Get a localized string for a key.
   * @param {String|de.elo.ix.client.ClientInfo} language Either an ISO language String, or an de.elo.ix.client.ClientInfo Object
   * @param {String} key The key in the resource files
   * @return {String}
   */
  getLocalizedString: function (language, key) {
    return sol.common.TranslateTerms.getTerm(language, key);
  },

  /**
   * Writes a feed event for an object.
   *
   * Uses {@link sol.common.ix.functions.FeedComment#RF_sol_function_FeedComment RF_sol_function_FeedComment}.
   *
   * @param {String} objId
   * @param {Object} params
   * @param {String} params.file The name of the language file (in `Administration/ELOwf Base/Feed/Script Locales`)
   * @param {String} params.key The key in the language file
   * @param {String[]} params.data (optional) Optional data, if the language key contains placeholders
   */
  writeFeedEvent: function (objId, params) {
    var me = this;
    if (!objId) {
      me.logger.error("IllegalArgumentException: can not write feed comment without an 'objId'");
      return;
    }
    if (!params || !params.file || !params.key) {
      me.logger.error("IllegalArgumentException: can not write feed comment without a 'file' or a 'key'");
      return;
    }
    sol.common.IxUtils.execute("RF_sol_function_FeedComment", params);
  },

  /**
   * Starts a workflow and returns the new workflow Id.
   * Uses {@link sol.common.WfUtils#startWorkflow WfUtils.startWorkflow}.
   *
   *
   *
   * @param {String} objId
   * @param {String} templateId
   * @param {String} name The workflow name
   * @return {String} The workflow ID
   */
  startWorkflow: function (objId, templateId, name) {
    return sol.common.WfUtils.startWorkflow(templateId, name, objId);
  },

  /**
   * Starts the workflow defined in the as standard workflow for the mask or in an index field.
   * If there is already an workflow, it will do nothing and return the first active one.
   * Uses {@link sol.common.WfUtils#startMaskStandardWorkflow WfUtils.startMaskStandardWorkflow}.
   *
   * @param {String} objId
   * @param {Object} params (optional) Default will be the sord name
   * @param {Object} params.name (optional) Default will be the sord name
   * @param {Object} params.field (optional) The field to read the workflow template from
   * @return {String} The workflow ID
   */
  startMaskStandardWorkflow: function (objId, params) {
    return sol.common.WfUtils.startMaskStandardWorkflow(objId, params);
  },

  /**
   * @private
   * Builds a json response including events, messages.
   * @return {String}
   */
  buildResponse: function () {
    var me = this,
        response = {};

    if (me.arrayIsNotEmpty(me._registeredEvents)) {
      response.events = me._registeredEvents;
    }

    if (me.arrayIsNotEmpty(me._messages)) {
      response.messages = me._messages;
    }

    response.data = me._data;

    return sol.common.JsonUtils.stringifyAll(response);
  },

  /**
   * @abstract
   * @private
   * @param {String} type The type of event which should be executed after function returns
   * @param {Object} params Object with key-value-pairs to configure the event
   * @param {Object} on (optional) Object with conditions for the event execution (see class documentation)
   */
  createEvent: function () {
    throw "cannot call 'createEvent' of class 'sol.common.ActionBase', function has to be implemented by subclass";
  },

  /**
   * @private
   * Checks if an Array is empty or not.
   * @param {Array} array
   * @returns {Boolean}
   */
  arrayIsNotEmpty: function (array) {
    return array && array.length > 0;
  }

});