    importPackage(javax.mail);
    importPackage(javax.mail.internet);
    importPackage(Packages.de.elo.ix.client);

    //@include lib_Class.js

    /**
     * Represents an email
     *
     * @author ELO Digital Office GmbH
     * @version 1.07.000
     *
     * @eloas
     * @eloix
     *
     * @requires sol.common.Config
     * @requires sol.common.Template
     * @requires sol.common.ObjectUtils
     * @requires sol.common.RepoUtils
     * @requires sol.common.SordUtils
     * @requires sol.common.WfUtils
     * @requires sol.common.UserUtils
     * @requires sol.common.ObjectFormatter
     *
     * The SMTP configuration is read from
     *   /Administration/Business Solutions Custom/common/Configuration/mail.config
     *
     * # SMTP sample configuration:
     *     {
     *        "smtpHost": "smtp.elo.local"
     *     }
     *
     * # Examples of the property 'to':
     *
     *   - "to": "user@domain.com"
     *     Send the email to the email adress "user@domain.com"
     *
     *   - "to": { type: "GRP", key: "CONTRACT_RESPONSIBLE" }
     *     Take the user or email adress from the index field "CONTRACT_RESPONSIBLE"
     *
     *   - "to": { type: "MAP", key: "USER" }
     *     Take the user or email adress from the map field "USER"
     *
     *   - "to": { type: "WFMAP", key: "USER" }
     *     Take the user or email adress from the workflow map field "USER"
     *
     *   - "to": { type: "CURRENT" }
     *     Send the email to the current node user
     *
     *   - "to": { type: "NEXT", key: "USER" }
     *     Send the email to the user of the next person node
     *
     *   - "to": { type: "WFOWNER" }
     *     Send the email to the workflow owner
     *
     *   - "to": { type: "SORDOWNER" }
     *     Send the email to the object owner
     *
     * If a field contains a "@" char, it's interpreted as an email address, otherwise it is
     * interpreted as an ELO user and the email adress will be retrieved from the user
     * profile.
     *
     */

    /**
     * This class allows sending E-Mails by using templates that are stored in the ELO repository.
     */
    sol.define("sol.common.Mail", {

      /**
       * @cfg {String} smtpHost
       * SMTP host
       */

      /**
       * @cfg {String} from
       * Sender
       */

      /**
       * @cfg {String} to
       * Recipients
       */

      /**
       * @cfg {String} cc
       * Carbon copy recipients
       */

      /**
       * @cfg {String} bcc
       * Blind carbon copy recipients
       */

      /**
       * @cfg {String} subject
       * Subject
       * Templating can be used, e.g. {{sord.name}}
       */

      /**
       * @cfg {Object} body
       * Body configuration
       *
       * @cfg {Object} body.type
       * Body type "html" or "text". Default is "text"
       *
       * @cfg {Object} body.tplObjId
       * Object ID of the body template
       */

      /**
       * @cfg {Object} data
       * This Object will be provided for the template processing.
       */

      /**
       * @cfg {String} objId
       * The data of this sord object will be provided for the template processing.
       */

      /**
       * @cfg {String} flowId
       * The data of this workflow will be provided for the template processing.
       */

      /**
       * @cfg {String} nodeId
       * The data of this workflow node will be provided for the template processing.
       */

      /**
       * @cfg {String} user
       * SMTP user
       */

      /**
       * @cfg {String} password
       * SMTP password
       */

      /**
       * @cfg {Boolean} useSsl
       * Use SSL
       */

      /**
       * @cfg {Boolean} useStartTls
       * Use STARTTLS
       */

      /**
       * @cfg {Boolean} trustAllHosts
       * Trust all hosts
       */

      /**
       * @cfg {Boolean} passwordEncrypted
       * password is encrypted
       */

      /**
       * @cfg {Boolean} debug
       * True if the SMTP debug information should be written into the log
       */

      /**
       * @cfg {Array} atts
       * Attachments
       *
       * # Example:
       *     "atts": [{"objId": "4309"},
       *              {"objId": "4309", "convertToPdf": true},
       *              {"objId": "4309", "convertToEcd": true}]
       *
       *  The convertToPDF is only working in the AS
       */

      /**
       * @cfg {Boolean} noWorkflowInfo
       * If true, the workflow information will not be loaded
       */

      /**
       * @cfg {Number} smtpTimeout
       * SMTP timeout
       */

      /**
       * @cfg {Number} smtpConnectionTimeout
       * SMTP connection timeout
       */

      /**
       * @cfg {Number} smtpWriteTimeout
       * SMTP write timeout
       */

      /**
       * @cfg {String} [sslProtocols=TLSv1.2]
       * Allowed SSL protocols
       */

      /**
       * @cfg {Boolean} [encodeFileName=false]
       * Encode any non-ASCII characters in the filename
       */

      /**
       * @private
       * @param {Object} config Configuration
       */
      initialize: function (config) {
        var me = this;
        me.$super("sol.Base", "initialize", [config]);
        me.logger.debug("config={{object}}", config);
      },

      /**
       * @private
       * Transfers the properties of the given configs into the current context
       * @param {Array} configs Configuations
       */
      transferConfigs: function () {
        var me = this,
            config, prop, i;
        for (i = 0; i < arguments.length; i++) {
          config = arguments[i];
          for (prop in config) {
            me[prop] = config[prop];
          }
        }
      },
      /**
       * @private
       * Initiates a SMTP session
       */
      initSession: function () {
        var me = this,
            socketFactory, props = new java.util.Properties(),
            authenticator = null,
            mailConfig;

        if (!me.smtpHost) {
          if (me.solutionNameForAsConfig) {
            mailConfig = me.loadMailConfig("/Administration/Business Solutions/" + me.solutionNameForAsConfig + "/Configuration/mail.config");
          }
          if (!mailConfig) {
            mailConfig = me.loadMailConfig("/Administration/Business Solutions/common/Configuration/mail.config");
          }
        }

        mailConfig = mailConfig || {};

        me.smtpHost = me.smtpHost || mailConfig.smtpHost;
        me.port = me.port || mailConfig.port;
        me.user = me.user || mailConfig.user;
        me.password = me.password || mailConfig.password;
        me.useSsl = (typeof me.useSsl != "undefined") ? me.useSsl : mailConfig.useSsl;
        me.useStartTls = (typeof me.useStartTls != "undefined") ? me.useStartTls : mailConfig.useStartTls;

        me.smtpTimeout = (me.smtpTimeout || mailConfig.smtpTimeout || 30000) + "";
        me.smtpConnectionTimeout = (me.smtpConnectionTimeout || mailConfig.smtpConnectionTimeout || 30000) + "";
        me.smtpWriteTimeout = (me.smtpWriteTimeout || mailConfig.smtpWriteTimeout || 30000) + "";

        me.trustAllHosts = (typeof me.trustAllHosts != "undefined") ? me.trustAllHosts : mailConfig.trustAllHosts;
        me.trustAllHosts = (typeof me.trustAllHosts != "undefined") ? me.trustAllHosts : true;

        me.passwordEncrypted = (typeof me.passwordEncrypted != "undefined") ? me.passwordEncrypted : mailConfig.passwordEncrypted;

        me.encodeFileName = (typeof me.encodeFileName != "undefined") ? me.encodeFileName : mailConfig.encodeFileName;

        if (!me.smtpHost) {
          throw "SMTP host must be set.";
        }
        props.put("mail.smtp.host", me.smtpHost);
        props.put("mail.smtp.localhost", java.net.InetAddress.localHost.hostName);
        if (me.password) {
          props.put("mail.smtp.auth", "true");
          authenticator = new javax.mail.Authenticator(me);
        }
        if (me.useSsl || me.useStartTls) {
          if (me.useSsl) {
            me.port = me.port || "465";
            props.put("mail.smtp.ssl.enable", "true");
          } else {
            me.port = me.port || "587";
            props.put("mail.smtp.starttls.enable", "true");
          }
          if (me.trustAllHosts) {
            socketFactory = new Packages.com.sun.mail.util.MailSSLSocketFactory();
            socketFactory.trustAllHosts = true;
            props.put("mail.smtp.ssl.socketFactory", socketFactory);
          }
          me.sslProtocols = mailConfig.sslProtocols || "TLSv1.2";
        } else {
          me.port = me.port || "25";
        }

        props.put("mail.smtp.port", me.port);

        props.put("mail.smtp.timeout", me.smtpTimeout);
        props.put("mail.smtp.connectiontimeout", me.smtpConnectionTimeout);
        props.put("mail.smtp.writetimeout", me.smtpWriteTimeout);

        if (me.encodeFileName) {
          props.put("mail.mime.encodefilename", "true");
        }

        me.logger.info("Start SMTP session: " + props);
        me.session = javax.mail.Session.getInstance(props, authenticator);

        me.logger.debug(["JavaMail: version={0}, library.path={1}", me.session.getClass().package.implementationVersion, me.session.getClass().protectionDomain.codeSource.location.path]);
        me.logger.debug(["Supported SSL protocols: {0}", java.lang.String.join(" ", javax.net.ssl.SSLContext.default.supportedSSLParameters.protocols)]);

        if ((me.useSsl || me.useStartTls) && me.sslProtocols) {
          me.logger.debug(["Allowed SMTP SSL protocols: {0}", me.sslProtocols]);
          props.setProperty("mail.smtp.ssl.protocols", me.sslProtocols);
        }

        if (me.debug) {
          me.session.debug = true;
          me.outputStream = new ByteArrayOutputStream();
          me.session.setDebugOut(new PrintStream(me.outputStream));
        }
      },

      /**
       * Loads the mail configuration
       * @param {String} repoPath
       * @return {Object} Mail configuration
       */
      loadMailConfig: function (repoPath) {
        var conn, objId, config;

        conn = (typeof ixConnectAdmin !== "undefined") ? ixConnectAdmin : ixConnect;

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

        if (!objId) {
          return;
        }

        config = sol.create("sol.common.Config", { compose: objId, connection: conn }).config;

        return config;
      },

      /**
       * @private
       * Retrieves the data that is provided for templating
       */
      getData: function () {
        var me = this,
            wfDiagram;

        me.body = me.body || {};
        me.data = me.data || me.body.data || {};

        if (me.objId) {
          me.sord = ixConnect.ix().checkoutSord(me.objId, EditInfoC.mbSord, LockC.NO).sord;
          me.data.sord = sol.common.SordUtils.getTemplateSord(me.sord).sord;
        }

        if (me.flowId && me.nodeId && !me.noWorkflowInfo) {
          wfDiagram = me.getWorkflow();
          me.data.node = sol.common.WfUtils.getTemplateWfDiagramNode(wfDiagram, String(me.nodeId)).node;
        }
      },

      getValue: function (def) {
        var me = this,
            value, wfDiagram, wfNode, successorNodes;

        if (!def) {
          return "";
        }

        if (sol.common.ObjectUtils.isObject(def)) {
          if (!def.type) {
            throw "to.type is empty";
          }

          me.logger.debug("valueConfig=" + JSON.stringify(def));

          switch (String(def.type).toUpperCase()) {

            case "GRP":
              me.checkKey(def);
              value = sol.common.SordUtils.getValue(me.sord, def);
              break;

            case "MAP":
              me.checkKey(def);
              value = sol.common.SordUtils.getValue(me.sord, def);
              break;

            case "SORDOWNER":
              value = me.sord.ownerName;
              break;

            case "WFMAP":
              me.checkKey(def);
              me.checkWorkflowProps();
              value = sol.common.WfUtils.getWfMapValue(me.flowId, def.key);
              break;

            case "CURRENT":
              wfDiagram = me.getWorkflow();
              wfNode = sol.common.WfUtils.getNode(wfDiagram, me.nodeId);
              if (wfNode) {
                value = wfNode.userName;
              } else {
                me.logger.warn(["Can't determinate workflow node: flowId={0}, nodeId={1}", wfDiagram.id, me.nodeId]);
              }
              break;

            case "NEXT":
              wfDiagram = me.getWorkflow();
              successorNodes = sol.common.WfUtils.getSuccessorNodes(wfDiagram, me.nodeId, WFNodeC.TYPE_PERSONNODE);
              if (!successorNodes || (successorNodes.length == 0)) {
                throw "No appropriate successor node found";
              }
              if (successorNodes.length > 1) {
                throw "Successor node is ambiguous";
              }
              value = successorNodes[0].userName;
              break;

            case "WFOWNER":
              wfDiagram = me.getWorkflow();
              value = wfDiagram.ownerName;
              break;

            default:
              throw "def.type=" + def.type + " is unsupported";
          }
        } else {
          value = def;
        }

        return value;
      },

      /**
       * @private
       * Retrieves the recipient of the mail
       * @param {Object|String} to Recipient definition
       * @return {String} recipient
       */
      getRecipient: function (to) {
        var me = this, recipient;

        recipient = me.getValue(to);

        if (!recipient) {
          throw "Recipient user is empty";
        }

        if (recipient.indexOf("@") == -1) {
          recipient = sol.common.UserUtils.getMailAddress(recipient);
        }

        if (!recipient) {
          throw "Recipient is empty";
        }

        return recipient;
      },

      /**
       * @private
       * Retrieves the sender of the mail
       * @param {Object|String} from Sender definition
       * @return {String} sender
       */
      getSender: function (from) {
        var me = this, sender;

        sender = me.getValue(from);

        if (!sender) {
          throw "Sender user is empty";
        }

        if (sender.indexOf("@") == -1) {
          sender = sol.common.UserUtils.getMailAddress(sender);
        }

        if (!sender) {
          throw "Sender is empty";
        }

        return sender;
      },

      /**
       * @private
       * Retrieves the subject of the mail
       * @param {Object|String} subj subject definition
       * @return {String} subject
       */
      getSubject: function (subj) {
        var me = this,
            subject, tpl;

        if (!(subject = me.getValue(subj))) {
          throw "subject is empty";
        }

        if (subject.indexOf("{{") > -1) {
          tpl = sol.create("sol.common.Template", { source: subject });
          subject = tpl.apply(me.data);
        }

        return subject;
      },

      /**
       * @private
       * @param {Object} to Recipient configuration
       * Checks the key value
       */
      checkKey: function (to) {
        if (!to.key) {
          throw "to.key is empty";
        }
      },

      /**
       * @private
       * Checks the me.flowId property
       */
      checkWorkflowProps: function () {
        var me = this;
        if (!me.flowId) {
          throw "flowId is empty";
        }
      },

      /**
       * @private
       * Returns the workflow diagram
       * @return {de.elo.ix.client.WFDiagram}
       */
      getWorkflow: function () {
        var me = this;

        if (!me.wfDiagram) {
          if (!me.flowId) {
            throw "flowId is empty";
          }
          if (!me.nodeId) {
            throw "nodeId is empty";
          }

          me.wfDiagram = ixConnect.ix().checkoutWorkFlow(me.flowId, WFTypeC.ACTIVE, WFDiagramC.mbAll, LockC.NO);
        }

        return me.wfDiagram;
      },

      /**
       * @private
       * @return {javax.mail.PasswordAuthentication} Authententication
       */
      getPasswordAuthentication: function () {
        var me = this,
            des, password;

        if (me.passwordEncrypted) {
          des = new Packages.de.elo.utils.sec.DesEncryption();
          password = des.decrypt(me.password);
        } else {
          password = me.password;
        }
        return new javax.mail.PasswordAuthentication(me.user, password);
      },

      /**
       * Sends an email
       */
      send: function () {
        var me = this,
            message, multiPart,
            i, attConfig, sord, attInputStream, dataSource, attPart, subject;

        if (!me.from) {
          throw "'From' is empty.";
        }
        me.initSession();
        me.getData();
        me.recipient = me.getRecipient(me.to);

        if (!me.recipient) {
          throw "Recipient is empty.";
        }

        me.cc = me.cc || "";
        me.cc && (me.recipientCc = me.getRecipient(me.cc));

        me.bcc = me.bcc || "";
        me.bcc && (me.recipientBcc = me.getRecipient(me.bcc));

        message = new MimeMessage(me.session);
        message.setFrom(new InternetAddress(me.getSender(me.from)));

        me.recipient.split(";").forEach(function (toPart) {
          message.addRecipient(Message.RecipientType.TO, new InternetAddress(toPart));
        });
        me.recipientCc && me.recipientCc.split(";").forEach(function (ccPart) {
          message.addRecipient(Message.RecipientType.CC, new InternetAddress(ccPart));
        });
        me.recipientBcc && me.recipientBcc.split(";").forEach(function (bccPart) {
          message.addRecipient(Message.RecipientType.BCC, new InternetAddress(bccPart));
        });

        subject = me.getSubject(me.subject) || "";
        message.setSubject(subject, "UTF-8");
        multiPart = new MimeMultipart();
        me.addBody(multiPart);
        if (me.atts) {
          for (i = 0; i < me.atts.length; i++) {
            attConfig = me.atts[i];
            if (attConfig.objId == "CURRENT") {
              if (me.sord && sol.common.SordUtils.isDocument(me.sord)) {
                attConfig.objId = me.sord.id;
              } else {
                if (attConfig.convertToEcd && me.sord) {
                  attConfig.objId = me.sord.id;
                } else {
                  continue;
                }
              }
            }
            me.logger.debug(["Add attachment: objId={0}", attConfig.objId]);
            sord = ixConnect.ix().checkoutSord(attConfig.objId, EditInfoC.mbSordDoc, LockC.NO).sord;
            if (attConfig.convertToEcd) {
              attConfig.extension = "ECD";
            } else {
              attConfig.extension = sord.docVersion.ext;
            }
            attInputStream = me.getAttAsStream(attConfig);
            dataSource = new javax.mail.util.ByteArrayDataSource(attInputStream, "application/octet-stream");
            attInputStream.close();
            attPart = new MimeBodyPart();
            attPart.dataHandler = new javax.activation.DataHandler(dataSource);
            attPart.fileName = sol.common.FileUtils.sanitizeFilename(sord.name) + "." + attConfig.extension;
            multiPart.addBodyPart(attPart);
          }
        }
        message.setContent(multiPart);
        me.logger.info(["Send mail: recipient={0}, subject={1}", me.recipient, me.subject]);
        Transport.send(message);

        me.logJavaMailInfo();
      },

      /**
       * @private
       * @param {javax.mail.Multipart} multiPart MultiPart
       */
      addBody: function (multiPart) {
        var me = this,
            bodyContent, template, bodyPart;
        me.body = me.body || {};
        bodyContent = me.body.content || "";
        if (me.body.tplObjId) {
          template = sol.create("sol.common.Template", {});
          template.load(me.body.tplObjId);
          bodyContent = template.apply(me.data);
        }
        bodyPart = new MimeBodyPart();
        if (me.body.type == "html") {
          bodyPart.setContent(bodyContent, "text/html; charset=utf-8");
        } else {
          bodyPart.setContent(bodyContent, "text/plain");
        }
        multiPart.addBodyPart(bodyPart);
      },

      /**
       * @private
       */
      logJavaMailInfo: function () {
        var me = this;
        if (me.debug) {
          me.logger.info(me.outputStream);
        }
      },

      /**
       * @private
       * @param {object} attConfig Attachment configuration
       * @return {java.io.InputStream} Attachment input stream
       */
      getAttAsStream: function (attConfig) {
        var officeConverter, inputStream;
        if (attConfig.objId) {
          if (attConfig.convertToPdf) {
            officeConverter = sol.create("sol.common.as.functions.OfficeConverter", {
              openFromRepo: {
                objId: attConfig.objId
              },
              saveToStream: {
                format: "pdf"
              }
            });
            if (officeConverter.isSupported(attConfig.extension)) {
              inputStream = officeConverter.execute();
              attConfig.extension = "pdf";
              return inputStream;
            }
          }

          if (attConfig.convertToEcd) {
            return sol.common.FileUtils.getEcdAsStream(attConfig.objId);
          }
        }
        return sol.common.RepoUtils.downloadToStream(attConfig.objId);
      }
    });