# Standard modules

This document provides a detailed description of the following standard modules:

The standard modules elo, tfer, addr, notify, exif, and www are described in the JavaDoc for the internal ELOas interface available at http://www.forum.elo.com/javadoc/as/21/ (opens new window).

# cnt: ELO Counter Access

The standard module cnt enables access to ELOam counter variables.

# cnt: Available functions

Create counter: The createCounter() function creates a new counter with a start value that can be preset. If the counter already exists, it is reset.

createCounter: function (counterName, initialValue) {
    var counterInfo = new CounterInfo();
    counterInfo.setName(counterName);
    counterInfo.setValue(initialValue);
    var info = new Array(1);
    info[0] = counterInfo;
    ixConnect.ix().checkinCounters(info, LockC.NO);
},

Get counter value: The getCounterValue() function gets the current value of the specified counter. If the autoIncrement parameter is set to true, the counter value is automatically incremented as well.

getCounterValue: function (counterName, autoIncrement) {
    var counterNames = new Array(1);
    counterNames[0] = counterName;
    var counterInfo = ixConnect.ix().checkoutCounters(counterNames,
                                                      autoIncrement,
                                                      LockC.NO);
    return counterInfo[0].getValue();
},

Create tracking number from counter: You can use the getTrackId() function when you need a serial, automatically recognizable number. It reads the next counter value and codes a number with a prefix and a check digit. The generated string looks like this: <prefix><sequential number>C<check digit> ("ELO1234C0")

getTrackId: function (counterName, prefix) {
    var tid = cnt.getCounterValue(counterName, true);
    return cnt.calcTrackId(tid, prefix)
},

Create tracking number: You can use the calcTrackId() function when you need a serial, automatically recognizable number. It codes a number with a prefix and a check digit. The generated string looks like this: <prefix><sequential number>C<check digit> ("ELO1234C0")

calcTrackId: function (trackId, prefix) {
    var chk = 0;
    var tmp = trackId;
    while (tmp > 0) {
        chk = chk + (tmp % 10);
        tmp = Math.floor(tmp / 10);
    }
    return prefix + "" + trackId + "C" + (chk %10);
},

Search for tracking number in text: The findTrackId() function searches a text for a tracking number. The expected prefix and the length of the actual number can be controlled with a parameter. If the number has a variable length, the length parameter can be set to 0. If no appropriate result is found in the text, -1 is returned. Otherwise, the number value (and not the complete track ID) is returned.

findTrackId: function (text, prefix, length) {
    text = " " + text + " ";
    var pattern = "\\s" + prefix + "\\d+C\\d\\s";
    if (length > 0) {
        pattern = "\\s" + prefix + "\\d{" +
                  length + "}C\\d\\s";
    }
    var val = text.match(new RegExp(pattern, "g"));
    if (!val) {
        return -1;
    }
    for (var i = 0; i < val.length; i++) {
        var found = val[i];
        var number = found.substr(prefix.length + 1,
                                  found.length - prefix.length - 4);
        var checksum = found.substr(found.length - 2, 1);
        if (checkId(number, checksum)) {
            return number;
        }
    }
    return -1;
}

# db:DB Access

The DB Access standard module provides simple access to external databases. ODBC databases, as well as Microsoft SQL and Oracle SQL, are supported in the standard module. If other databases need to be accessed with a native JDBC driver, the corresponding JAR files must be copied to the LIB directory of the service, and the imports and access parameters saved to the Imports module. The order of database definitions in the imports module will then determine the value of the Connection number parameter in the following calls.

# db: Available functions

getColumn( connection number, SQL query );

This call must be provided as a parameter for an SQL query, which requests a column and returns only one row as result.

For example:

"select USERNAME from CUSTOMERS where USERID = 12345"

The connection number will determine which database connection is used. The list of available connections is defined in the imports module.

Example with JavaScript code:

var cmd = "select USERNAME from CUSTOMERS where USERID = 12345"
var res = getColumn(1, cmd)
log.debug(res)

Example in the GUI Designer:

GUI Designer

Fig.: GUI Designer

If the results list comprises multiple rows, only the first value is returned. All others are ignored without returning an error message.

getLine( connection number, SQL query );

This request returns a JavaScript object as the result with the values of the first row of the SQL query. The query can contain any number of columns, including an *. The column names, however, must be unique and valid JavaScript identifiers. Please note that the JavaScript identifiers are case sensitive.

For example:

"select USERNAME, STREET, CITY from CUSTOMERS where USERID = 12345"

The connection number will determine which database connection is used. The list of available connections is defined in the imports module.

Example with JavaScript code:

var cmd =
  "SELECT objshort, objidate, objguid FROM [elo20].[dbo].objekte where objid = 22"
var res = getLine(1, cmd)
log.debug(res.objshort)
log.debug(res.objidate)
log.debug(res.objguid)

If the results list contains multiple rows, only the values of the first row are returned. All other rows are ignored without returning an error message.

getMultiLine(connection number, SQL command, maximum number of rows)

This command works in a similar way to the getLine request. However, it returns an array of objects instead of a single object. Each row in the results list creates an entry in the array. To prevent buffer overflows in case of large databases and poorly formed queries, you can limit the maximum number of rows. Additional results are simply ignored.

Example:

var obj = db.getMultiLine(1, "select objshort, objid from [elo80].[dbo].objekte where objid < 100 order by objshort", 50);
    for (var lg = 0; lg < obj.length; lg++) {
        log.debug(obj[lg].objid + " : " + obj[lg].objshort);
    }
doUpdate(connection number, SQL command)

The getLine or getColumn calls cannot be "abused" to make changes to the database. This command uses the internal JDBC command executeQuery, which only permits SELECT queries.

In order to change an entry, the doUpdate call can be used. This transfers the entered SQL command to the JDBC command executeUpdate, which can be used to change existing entries or insert new entries.

Information

As all parameters have to be transferred in text format, be careful to correctly code any quotation marks that may occur. Otherwise, error messages will occur, and in the worst case scenario, this could even lead to an SQL injection attack on the SQL server.

# Imports

The type and scope of required imports depend on the database and can be found in the manufacturer's documentation. The JAR files in use may need to be copied to the LIB directory of the ELOas service.

The following shows an example of the necessary imports for the JDBC-ODBC bridge:

importPackage(Packages.sun.jdbc.odbc);

A standard system selector was introduced to the Imports module of the standard ELOas 12 libraries. For performance reasons, the standard system selector has the standard value SordC.mbLean and is used when processing available ELOas rules.

const EM_SYS_STDSEL = SordC.mbLean;

A system selector named EM_SYS_SELECTOR was also introduced to the Imports module. The system selector is set to the value of the set standard system selector in the bt module. In the onStart event of the ELOas rules, the system selector can use/process additional properties of an entry, besides the ID and name.

EM_SYS_SELECTOR=SordC.mbAll;

At the same time, a workflow selector named EM_WF_SELECTOR was added to the workflow constants:

var EM_WF_SELECTOR = SordC.mbLean;

# Connection parameters

The database connection parameters must be saved in the Imports module. There you can find a list of connections that can be later addressed using their number (starting with 0) as connection number.

var EM_connections = [
    {
        driver: 'sun.jdbc.odbc.JdbcOdbcDriver',
        url: 'jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=C:\\Temp\\EMDemo.mdb',
        user: '',
        password: '',
        initdone: false,
        classloaded: false,
        dbcn: null
    },
    {
        driver: 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
        url: 'jdbc:sqlserver://srvt02:1433',
        user: 'elodb',
        password: 'elodb',
        initdone: false,
        classloaded: false,
        dbcn: null
    }
];

The following information must be entered for each connection:

driver JDBC class name for the database connection. You can get this information from the JDBC driver provider or from the database provider.
url Access URL to the database. Database-dependent connection parameters are configured here, such as file paths for Access databases, or server names and ports for SQL databases. These connection parameters are manufacturer-dependent and can be found in the corresponding documentation.
user Login name for database access. This parameter is not used by all databases (e.g. not by unprotected Access databases). In such cases, the parameter can remain empty.
password Database password.
initdone Internal variable for "lazy initialization".
classloaded Internal variable to check whether the class file has already been loaded.
dbcn Internal variable to save the database connection object.

# JavaScript code

The dbInit routine is only called within the module. It is performed before each database access, and checks whether a connection has been established, establishing one if necessary.

function dbInit(connectId) {
  if (EM_connections[connectId].initdone == true) {
    return
  }
  log.debug("Now init JDBC driver")
  var driverName = EM_connections[connectId].driver
  var dbUrl = EM_connections[connectId].url
  var dbUser = EM_connections[connectId].user
  var dbPassword = EM_connections[connectId].password
  try {
    if (!EM_connections[connectId].classloaded) {
      Class.forName(driverName).newInstance()
      log.debug("Register driver ODBC")
      DriverManager.registerDriver(new JdbcOdbcDriver())
      EM_connections[connectId].classloaded = true
    }
    log.debug("Get Connection")
    EM_connections[connectId].dbcn = DriverManager.getConnection(
      dbUrl,
      dbUser,
      dbPassword
    )
    log.debug("Init done.")
  } catch (e) {
    log.debug("ODBC Exception: " + e)
  }
  EM_connections[connectId].initdone = true
}

The exitRuleset_DB_Access() function is called automatically once the ruleset is finished processing. It checks whether a connection exists, then closes it. This check must take place for all configured databases.

function exitRuleset_DB_Access() {
  log.debug("dbExit")
  for (i = 0; i < EM_connections.length; i++) {
    if (EM_connections[i].initdone) {
      if (EM_connections[i].dbcn) {
        try {
          EM_connections[i].dbcn.close()
          EM_connections[i].initdone = false
          log.debug("Connection closed: " + i)
        } catch (e) {
          log.info("Error closing database " + i + ": " + e)
        }
      }
    }
  }
}

The function getLine() reads a line from the database with any number of columns, then packs the results into a JavaScript object. This object then receives a member variable with the column name for each column.

function getLine(connection, qry) {
  // Sub-function: creates a JavaScript object with
  // the imported database contents
  function dbResult(connection, qry) {
    // First establish the connection
    dbInit(connection)
    // Now create a SQL statement object
    var p = EM_connections[connection].dbcn.createStatement()
    // And execute the query
    var rss = p.executeQuery(qry)
    // rss contains the list of results. Now the first
    // row is read
    if (rss.next()) {
      // The number of columns is identified via the metadata
      var metaData = rss.getMetaData()
      var cnt = metaData.getColumnCount()
      // A member variable is created for each column
      // It has the SQL column name as the name and
      // imported database contents as the variable.
      // Additionally, the first column can always be addressed
      // under the name 'first'.
      for (i = 1; i <= cnt; i++) {
        var name = metaData.getColumnName(i)
        var value = rss.getString(i)
        this[name] = value
        if (i == 1) {
          this.first = value
        }
      }
    }
    // Finally, the list of results and the SQL
    // statement are closed.
    rss.close()
    p.close()
  }
  // the actual function's start is here. A
  // JavaScript object with the database contents
  // is requested.
  var res = new dbResult(connection, qry)
  return res
}
// The getColumn function is a special variant
// of the getLine call. The SQL query can only
// show one column as a result. If there are more
// columns, these will be ignored, along with
// additional rows.
function getColumn(connection, qry) {
  var res = getLine(connection, qry)
  return res.first
}

# dex: Document Export

The Document Export module can automatically export documents from the repository to the file system. This export is not a one-time process – if a new document version is created, the module automatically writes an updated file. Further, published files can be deleted. For security reasons, the files can only be placed in a preconfigured path.

To use this module, a metadata form must be defined that contains the document status and one or more filing targets in the file system. In addition, the document number of the most recent export will be saved in the form.

Status field in the metadata form

Fig.: Status field in the metadata form

The status field determines the actions to be performed. Active: Released registers the file for export. Active: Set for deletion deletes the file in the file system and sets the status to No longer active/deleted. All other status settings do not trigger an ELOas action and are intended for internal documents or documents that have not yet been released. As this status value is queried for internal processing, it is a good idea to only enter values to this line from a preconfigured keyword list.

The fields File path 1..5 contain the path and file name of the document in the file system. Note that this is a relative path, where the starting path is preset as a fixed value called dexRoot in the JavaScript module and can be changed there. This fixed value is designed for security purposes, as otherwise user error could lead to files being overwritten.

The Last export field contains the document number of the most recently exported file version. If a file is edited, creating a new version, the module recognizes this and writes a new copy to the file system. This field is then refreshed.

If an error occurs during processing, the error rule enters the text "ERROR" to the Last export field. This allows you to create a dynamic index in ELO, which then checks this field for the value ERROR and thus always shows a current list of all documents that cannot be exported.

Example for a dynamic index when the form has an ID of 22:

!+ , objkeys where objmask = 22 and objid = parentid and okeyname ='PDEXPORT
        and okeydata ='ERROR'

# dex: Available functions

This module only provides one function: processDoc. It is assigned the ELO Indexserver SORD object as a parameter and, based on the status, checks whether the file should be exported or deleted, then performs the corresponding action. The new document ID is then transferred as return value. The current SORD object is available within a rule process in the JavaScript variable Sord.

Example in the XML ruleset code:

<rule>
    <name>Rule 1</name>
    <condition>(PDEXPORT != Sord.getDoc()) &amp;&amp; (PDEXPORT != "ERROR") || (PDSTATUS == "Active: Set for deletion") </condition>
    <index>
    <name>PDEXPORT</name>
    <value>dex.processDoc(Sord)</value>
    </index>
</rule>

# dex: JavaScript code

First, the base path docRoot for the document repository is identified. The target path is always ascertained from this setting and the user input in the metadata form. In principle, it would be possible to leave the base path empty, allowing the user to enter any path they wish. However, this approach would present a great security risk, as every user could overwrite any file from the access area of ELOas.

var dexRoot = "c:\\temp\\"

The processDoc function is called from the rule definition. The status of the ELO Indexserver SORD object is checked and the required function is called.

function processDoc(Sord) {
  log.debug("Status: " + PDSTATUS + ", Name: " + NAME)
  if (PDSTATUS == "Active: Set for deletion") {
    return dex.deleteDoc(Sord)
  } else if (PDSTATUS == "Active: Released") {
    return dex.exportDoc(Sord)
  }
  return ""
}

If the status was set to "Delete", the deleteDoc function initiates the deletion of the files and changes the status to "Deleted".

function deleteDoc(Sord) {
  dex.deleteFile(PDPATH1)
  dex.deleteFile(PDPATH2)
  dex.deleteFile(PDPATH3)
  dex.deleteFile(PDPATH4)
  dex.deleteFile(PDPATH5)
  PDSTATUS = "No longer active / deleted"
  return Sord.getDoc()
}

The deleteFile function performs the actual deletion. It first checks whether a file name is configured and whether the file exists, and then removes it from the file system.

function deleteFile(destPath) {
  if (destPath == "") {
    return
  }
  var file = new File(docRoot + destPath)
  if (file.exists()) {
    log.debug("Delete expired version: " + docRoot + destPath)
    file["delete"]()
  }
}

The internal exportDoc function is called to write new file versions. The file is retrieved by the document manager and copied to the target folder.

function exportDoc(Sord) {
  var editInfo = ixConnect
    .ix()
    .checkoutDoc(Sord.getId(), null, EditInfoC.mbSordDoc, LockC.NO)
  var url = editInfo.document.docs[0].getUrl()
  dex.copyFile(url, PDPATH1)
  dex.copyFile(url, PDPATH2)
  dex.copyFile(url, PDPATH3)
  dex.copyFile(url, PDPATH4)
  dex.copyFile(url, PDPATH5)
  return Sord.getDoc()
}

The copyFile function executes the copying process on the target folder. It first checks whether a target file name already exists and if an older version exists that has to be deleted. The new version is then retrieved by the document manager and saved in the target folder.

function copyFile(url, destPath) {
    if (destPath == "") {
        return;
    }
    log.debug("Path: " + docRoot + destPath);
    var file = new File(docRoot + destPath);
    if (file.exists()) {
        log.debug("Delete old version.");
        file["delete"](#ELODOC-D50FBC7EA85D4A709D2C12762E1B9F300);
}

# ix: IndexServer functions

The ELOix module contains a collection of various ELO Indexserver functions that are required frequently in scripting. However, most of these are simple wrappers to perform a similar ELO Indexserver command, and not complex new functions in themselves.

# ix: Available functions

Delete a Sord entry : The object IDs of the SORD entry to be deleted and its parent entry must be passed as parameters to the deleteSord() function.

lookupIndex: function (archivePath) {
    log.info("Lookup Index: " + archivePath);
    var editInfo = ixConnect.ix().checkoutSord("ARCPATH:" + archivePath,
                                               EditInfoC.mbOnlyId, LockC.NO);
    if (editInfo) {
        return editInfo.getSord().getId();
    }   else {
        return 0;
    }
}

Search for an entry : The lookupIndex() function identifies the object ID of an entry found via the filing path. The archivePath parameter must start with a separator.

lookupIndex: function (archivePath) {
    log.info("Lookup Index: " + archivePath);
    var editInfo = ixConnect.ix().checkoutSord("ARCPATH:" + archivePath, EditInfoC.mbOnlyId, LockC.NO);
    if (editInfo) {
        return editInfo.getSord().getId();
    }   else {
        return 0;
    }
}

Search for an entry: The lookupIndexByLine() function identifies the object ID of an entry based on a metadata field search. If the Mask ID parameter is transferred with an empty string, all metadata forms are searched. The group name and the search term must be provided.

lookupIndexByLine : function(maskId, groupName, value) {
    var findInfo = new FindInfo();
    var findByIndex = new FindByIndex();
    if (maskId != "") {
        findByIndex.maskId = maskId;
    }
    var objKey = new ObjKey();
    var keyData = new Array(1);
    keyData[0] = value;
    objKey.setName(groupName);
    objKey.setData(keyData);
    var objKeys = new Array(1);
    objKeys[0] = objKey;
    findByIndex.setObjKeys(objKeys);
    findInfo.setFindByIndex(findByIndex);
    var findResult = ixConnect.ix().findFirstSords(findInfo, 1, SordC.mbMin);
    ixConnect.ix().findClose(findResult.getSearchId());
    if (findResult.sords.length == 0) {
        return 0;
    }
    return findResult.sords[0].id;
},

Read the full text information: The getFulltext() function returns the current full text information for a document. The full text data is returned as a string.

Please note

It is not possible to tell whether no full text exists, whether full text processing has been completed, or if it was canceled with errors. The text that exists at the time the query is performed is returned (which may be an empty string if no full text information exists).

getFulltext: function(objId) {
    var editInfo = ixConnect.ix().checkoutDoc(objId, null, EditInfoC.mbSordDoc, LockC.NO);
    var url = editInfo.document.docs[0].fulltextContent.url
    var ext = "." + editInfo.document.docs[0].fulltextContent.ext
    var name = fu.clearSpecialChars(editInfo.sord.name);
    var temp = File.createTempFile(name, ext);
    log.debug("Temp file: " + temp.getAbsolutePath());
    ixConnect.download(url, temp);
    var text = FileUtils.readFileToString(temp, "UTF-8");
    temp["delete"](#ELODOC-D50FBC7EA85D4A709D2C12762E1B9F301)` checks whether the entered folder path exists in the repository and, if needed, creates any missing parts automatically.

js createSubPath: function (startId, destPath, folderMask) {

log.debug("createPath: " + destPath);
try {
    var editInfo = ixConnect.ix().checkoutSord("ARCPATH:" + destPath,
                                               EditInfoC.mbOnlyId, LockC.NO);
    log.debug("Path found, GUID: " + editInfo.getSord().getGuid() +
              " ID: " + editInfo.getSord().getId());
    return editInfo.getSord().getId();;
}   catch (e) {
    log.debug("Path not found, create new: " + destPath +
              ", use foldermask: " + folderMask);
}
items = destPath.split("¶");
var sordList = new Array(items.length - 1);
for (var i = 1; i < items.length; i++) {
log.debug("Split " + i + " : " + items[i]);
var sord = new Sord();
sord.setMask(folderMask);
sord.setName(items[i]);
sordList[i - 1] = sord;
}
log.debug("now checkinSordPath");
var ids = ixConnect.ix().checkinSordPath(startId, sordList,
        new SordZ(SordC.mbName | SordC.mbMask));
log.debug("checkin done: id: " + ids[ids.length - 1]);
return ids[ids.length - 1];
}

# wf: Workflow Utils

The wf module contains simplified access methods to workflow data. This is divided into two groups of functions:

The high level functions changeNodeUser and readActiveWorkflow are to be used for simple access from a running WORKFLOW process, and work with the currently active workflow. They are easy to use, but only perform a simple function.

The low level functions readWorkflow, writeWorkflow, unlockWorkflow, and getNodeByName can be used from any location. If you want to make multiple changes to the same workflow, you can ensure that the workflow will only be read and written once, and not multiple times for each operation.

# wf: Available functions

Change user name of a person node: The changeNodeUser(): function replaces the user in the current workflow node named nodeName with a new user - nodeUserName.

As this call reads, changes, and immediately rewrites the entire workflow, this simple call should only be used when only one node needs to be edited. If multiple changes are necessary, use the functions described later on to read, edit, and save a workflow.

As this function identifies the workflow ID from the currently active workflow, it can only be called from the "WORKFLOW" search. When using it in a TREEWALK or a normal search, a random workflow ID is used.

changeNodeUser: function(nodeName, nodeUserName) {
    var diag = wf.readActiveWorkflow(true);
    var node = wf.getNodeByName(diag, nodeName);
    if (node) {
        node.setUserName(nodeUserName);
        wf.writeWorkflow(diag);
    }   else {
        wf.unlockWorkflow(diag);
    }
}

Copy user name at a node: The copyNodeUser() function works in a similar way to changeNodeUser; however, it copies the user name from one node to another node.

copyNodeUser: function(sourceNodeName, destinationNodeName) {
    var diag = wf.readActiveWorkflow(true);
    var sourceNode = wf.getNodeByName(diag, sourceNodeName);
    var destNode = wf.getNodeByName(diag, destinationNodeName);
    if (sourceNode && destNode) {
        var user = sourceNode.getUserName();
        destNode.setUserName(user);
        wf.writeWorkflow(diag);
        return user;
    }   else {
        wf.unlockWorkflow(diag);
        return null;
    }
}

Read current workflow: The readActiveWorkflow() function reads the currently active workflow into a local variable for editing. At the end, it can be rewritten with writeWorkflow, or the lock can be removed with unlockWorkflow.

readActiveWorkflow: function(withLock) {
    var flowId = EM_WF_NODE.getFlowId();
    return wf.readWorkflow(flowId, withLock);
    },

Read workflow: The readWorkflow() function reads a workflow into a local variable. It can then be evaluated and changed. If you want to save the changes, it can be rewritten using writeWorkflow. If the workflow is locked and can be read but you do not want to save any changes, the lock can be removed with unlockWorkflow.

readWorkflow: function(workflowId, withLock) {
    log.debug("Read Workflow Diagram, WorkflowId = " + workflowId);
    return ixConnect.ix().checkoutWorkFlow(String(workflowId),
                                           WFTypeC.ACTIVE,
                                           WFDiagramC.mbAll,
                                           (withLock) ? LockC.YES : LockC.NO);
},

Rewrite workflow: The writeWorkflow() function writes the workflow from a local variable to the database. Any write lock set on it is reset automatically.

writeWorkflow: function(wfDiagram) {
    ixConnect.ix().checkinWorkFlow(wfDiagram, WFDiagramC.mbAll, LockC.YES);
},

Reset read lock: unlockWorkflow() function. If a workflow with a write lock has been read but cannot be changed, the lock can be reset with unlockWorkflow.

unlockWorkflow: function(wfDiagram) {
    ixConnect.ix().checkinWorkflow(wfDiagram, WFDiagramC.mbOnlyLock, LockC.YES);
},

Search workflow nodes: The getNodeByName() function searches the workflow node for a node name. The name must be unique, as otherwise the first node found will be returned.

getNodeByName: function(wfDiagram, nodeName) {
    var nodes = wfDiagram.getNodes();
    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        if (node.getName() == nodeName) {
            return node;
        }
    }
    return null;
},

Start workflow from template: The startWorkflow() function starts a new workflow for an ELO object ID from a workflow template.

startWorkflow: function(templateName, flowName, objectId) {
    return ixConnect.ix().startWorkFlow(templateName, flowName, objectId);
}

# mail: Mail Utils

This module is intended for sending e-mails. It requires an SMTP host, through which the e-mails can be sent. This host has to be made known before sending the first e-mail by using the setSmtpHost function. Messages can then be sent with SendMail or SendMailWithAttachment. The module consists of two parts: one for sending e-mails and one for reading e-mail mailboxes.

# mail: Available functions for reading a mailbox

You can define a ruleset so that a search is performed on a mailbox and not the ELO repository or ELO task list. A logon routine has to be configured in the module for each type of mailbox. In this function, the mail server must be contacted, the desired e-mail folder searched through, and the list of messages read. Afterwards, ELOas continues to process the command normally. A document is prepared for each e-mail in the folder designated in SEARCHVALUE. Next, the ruleset is executed (the subject of the e-mail is automatically applied to the short name field). If the entry is not saved at the end, there will be nothing to find in the repository either. Only saved e-mails are transferred to the repository.

<search>
<name>"MAILBOX_GMAIL"</name>
<value>"ARCPATH:¶ELOas¶IMAP"</value>
<mask>2</mask>

In the ruleset, MAILBOX_<connection name> must be defined as the name, and the repository path or the number of the target folder as the value. A metadata form to be used for new documents also has to be defined.

The e-mail is then processed in the ruleset script. The mail module offers a few help routines to simplify this. In the following example, the body of the e-mail message will be copied to the Extra text tab in the metadata. Sender, recipient, and MailID will be applied to the corresponding fields of the e-mail form:

OBJDESC = mail.getBodyText(MAIL_MESSAGE);
ELOOUTL1 = mail.getSender(MAIL_MESSAGE);
ELOOUTL2 = mail.getRecipients(MAIL_MESSAGE, "¶");
ELOOUTL3 = msgId;
EM_WRITE_CHANGED = true;

If additional values or information are required, a complete Java e-mail (Mime) message object is available in the MAIL_MESSAGE variable.

To ensure that processed e-mail messages are not transferred to the repository multiple times, you should perform a search for the MailID before you start processing. If the e-mail message is already in the repository, the variable MAIL_ALLOW_DELETE is set to true. Otherwise, the e-mail message is processed. By setting the deletion flag, the e-mail is either removed from the mailbox or marked as processed during transfer.

var msgId = MAIL_MESSAGE.messageID;
if (ix.lookupIndexByLine(EM_SEARCHMASK, "ELOOUTL3", msgId) != 0) {
    log.debug("Mail bereits im Repository vorhanden, Ignorieren oder Löschen");
    MAIL_ALLOW_DELETE = true;
}   else {
    OBJDESC = mail.getBodyText(MAIL_MESSAGE);
    ELOOUTL1 = mail.getSender(MAIL_MESSAGE);
    ELOOUTL2 = mail.getRecipients(MAIL_MESSAGE, "¶");
    ELOOUTL3 = msgId;
    EM_WRITE_CHANGED = true;
}

This approach only reads an e-mail twice (once for normal processing, and once in the next pass for deletion), but it has the great advantage of ensuring the e-mail is only deleted from the mailbox if it definitely exists in the repository.

If you want to use a mailbox for monitoring, the following four functions are required in the JavaScript library 'mail':

Establish connection, open mailbox folder: nextImap_<connection name>

Next message in the list for processing: finalizeImap_<connection name>

Mark message as processed or delete: finalizeImap_<connection name>

Close connection: closeImap_<connection name>

In simple cases, only one of these four functions needs to be implemented: establish connection – connectImap_<Verbindungsname>. As a complete range of project-specific actions takes place here (login parameters, searching for target folder), there is no standard implementation. The three other functions already exist with a standard function in the system. You simply need to implement them to perform these additional functions.

Connect to IMAP server: connectImap_<connection name>(): This function must establish a connection with the e-mail server, search for the desired mailbox, and read it. Existing messages are saved to the variable MAIL_MESSAGES. The e-mail store must be saved to the variable MAIL_STORE and the folders that are read out to the variable MAIL_INBOX. Both of these values are required at the end of processing to close the connection. The variable MAIL_DELETE_ARCHIVED determines whether messages can be deleted from the mailbox. If set to false, deletion requests from the ruleset are ignored. This function will not be directly called up via a script, but rather activated internally in ELOas (in the MAILBOX search, in the example MAILBOX_GMAIL).

connectImap_GMAIL: function() {
    var props = new Properties();
    props.setProperty("mail.imap.host", "imap.gmail.com");
    props.setProperty("mail.imap.port", "993");
    props.setProperty("mail.imap.connectiontimeout", "5000");
    props.setProperty("mail.imap.timeout", "5000");
    props.setProperty("mail.imap.socketFactory.class",
                      "javax.net.ssl.SSLSocketFactory");
    props.setProperty("mail.imap.socketFactory.fallback", "false");
    props.setProperty("mail.store.protocol", "imaps");
    var session = Session.getDefaultInstance(props);
    MAIL_STORE = session.getStore("imaps");
    MAIL_STORE.connect("imap.gmail.com", "<<<USERNAME>>>@gmail.com",
                       "<<<PASSWORT>>>");
    var folder = MAIL_STORE.getDefaultFolder();
    MAIL_INBOX = folder.getFolder("INBOX");
    MAIL_INBOX.open(Folder.READ_WRITE);
    MAIL_MESSAGES = MAIL_INBOX.getMessages();
    MAIL_POINTER = 0;
    MAIL_DELETE_ARCHIVED = false;
},

Close connection: The closeImap_<Verbindungsname> function is optional and closes the current connection to the IMAP server. If no special tasks need to be performed on closing, you do not need to implement this function. Instead, the standard implementation closeImap() from the library is used. This closes the folder and the store.

closeImap_GMAIL: function() {
    // hier können eigene Aktionen vor dem Schließen ausgeführt werden
    // Standardaktion, Folder und Store schließen.
    MAIL_INBOX.close(true);
    MAIL_STORE.close();
},

Mark message as processed or delete: The finalizeImap_<connection name>() function is optional and deletes the current message, or otherwise marks it as processed. If it is not implemented, ELOam uses the standard implementation, which deletes a processed e-mail from the folder.

The following example does not delete the e-mail, but rather sets it to "read".

finalizeImap_GMAIL: function() {
    if (MAIL_DELETE_ARCHIVED && MAIL_ALLOW_DELETE) {
        message.setFlag(Flags.Flag.SEEN, true);
    }
},

Process next message in the list: The nextImap_<connection name> function is optional and returns the next message in the selected mailbox to the ruleset for processing. If the function is not implemented, ELOas will use the standard implementation, which sends every document for processing.

The example shows an implementation that only processes unread e-mails. They can be used in pairs with the finalizeImap implementation above, which sets e-mails as read rather than deleting them.

Please note

If you work with this method, you must use another way to ensure that the mailbox does not grow too large (such as by deleting automatically after a certain date).

nextImap_GMAIL: function() {
    if (MAIL_POINTER > 0) {
        mail.finalizePreviousMessage(MAIL_MESSAGE);
    }
    for (;;) {
        if (MAIL_POINTER >= MAIL_MESSAGES.length) {
            return false;
        }
        MAIL_MESSAGE = MAIL_MESSAGES[MAIL_POINTER];
        var flags = MAIL_MESSAGE.getFlags();
        if (flags.contains(Flags.Flag.SEEN)) {
            MAIL_POINTER++;
            continue;
        }
        MAIL_ALLOW_DELETE = false;
        MAIL_POINTER++;
        return true;
    }
    return false;
},

Read e-mail body text: The getBodyText() function transfers the message as a parameter (available in the script via the variable MAIL_MESSAGE) and returns the mail body as return parameter. It also searches for the first MIME part of type TEXT/PLAIN. If no corresponding part exists, an empty string is returned.

getBodyText: function(message) {
    var content = message.content;
    if (content instanceof String) {
        return content;
    }   else if (content instanceof Multipart) {
        var cnt = content.getCount();
        for (var i = 0; i < cnt; i++) {
            var part = content.getBodyPart(i);
            var ct = part.contentType;
            if (ct.match("^TEXT/PLAIN") == "TEXT/PLAIN") {
                return part.content;
            }
        }
    }
    return "";
},

Identify sender: The getSender() function returns the e-mail address of the sender.

getSender: function(message) {
    var adress = message.sender;
    return adress.toString();
},

Identify recipient: The getRecipients() function returns a list of all recipients (TO and CC). If there is more than one recipient, the list is provided in column index format, assuming that the ELO separator symbol ¶ is transferred in the 'delimiter' parameter.

getRecipients: function(message, delimiter) {
    var adresses = message.allRecipients;
    var cnt = 0;
    if (adresses) { cnt = adresses.length; }
    var hasMany = cnt > 1;
    var result = "";
    for (var i = 0; i < cnt; i++) {
        if (hasMany) { result = result + delimiter; }
        result = result + adresses[i].toString();
    }
    return result;
}

# Available functions for sending e-mails

The send functions are not used directly by ELOas. They are utility functions for custom script programming to conceal the complexity of the Java mail API from the script developer.

Register SMTP server: The setSmtpHost() function registers the library of the SMTP host to be used. This library is used to send e-mails. This function must be activated before the first sendMail call.

setSmtpHost: function(smtpHost) {
    if (MAIL_SMTP_HOST != smtpHost) {
        MAIL_SMTP_HOST = smtpHost;
        MAIL_SESSION = undefined;
    }
},

Send e-mail: The sendMail() function sends an e-mail. The sender and recipient addresses are transferred as parameters, in addition to the subject and e-mail text.

sendMail: function(addrFrom, addrTo, subject, body) {
    mail.startSession();
    var msg = new MimeMessage(MAIL_SESSION);
    var inetFrom = new InternetAddress(addrFrom);
    var inetTo = new InternetAddress(addrTo);
    msg.setFrom(inetFrom);
    msg.addRecipient(Message.RecipientType.TO, inetTo);
    msg.setSubject(subject);
    msg.setText(body);
    Transport.send(msg);
},

Send e-mail with attachment: The sendMailWithAttachment() function sends an e-mail. The sender and recipient addresses are transferred as parameters, in addition to the subject, e-mail text, and the object ID of the attachment from ELO. The attachment is stored as a temporary file in a temporary path; sufficient space must be available at this location.

sendMailWithAttachment: function(addrFrom, addrTo, subject, body, attachId) {
    mail.startSession();
    var temp = fu.getTempFile(attachId);
    var msg = new MimeMessage(MAIL_SESSION);
    var inetFrom = new InternetAddress(addrFrom);
    var inetTo = new InternetAddress(addrTo);
    msg.setFrom(inetFrom);
    msg.addRecipient(Message.RecipientType.TO, inetTo);
    msg.setSubject(subject);
    var textPart = new MimeBodyPart();
    textPart.setContent(body, "text/plain");
    var attachFilePart = new MimeBodyPart();
    attachFilePart.attachFile(temp);
    var mp = new MimeMultipart();
    mp.addBodyPart(textPart);
    mp.addBodyPart(attachFilePart);
    msg.setContent(mp);
    Transport.send(msg);
    temp["delete"]();
}

# fu: File Utils

The File Utils functions help ELOas users with file operations.

# fu: Available functions

Clean up file name: If you want to create a file name from the short name, it may contain critical characters that can lead to problems in the file system (e.g. colon, backslash, and ampersand). The clearSpecialChars() function replaces all characters other than numbers and letters with an underscore (including umlauts and ß).

clearSpecialChars: function(fileName) {
    var newFileName = fileName.replaceAll("\\W", "_");
    return newFileName;
},

Load document file: The getTempFile() function downloads the document file for the specified ELO object to the local file system (in the ELOas temp folder). If the file is no longer required, it must be removed again by the script developer using the function deleteFile. Otherwise, it will remain on the hard drive.

Please note

This returns a Java file object, not a file name.

getTempFile: function(sordId) {
    var editInfo = ixConnect.ix().checkoutDoc(sordId, null,
                                              EditInfoC.mbSordDoc, LockC.NO);
    var url = editInfo.document.docs[0].url;
    var ext = "." + editInfo.document.docs[0].ext;
    var name = fu.clearSpecialChars(editInfo.sord.name);
    var temp = File.createTempFile(name, ext);
    log.debug("Temp file: " + temp.getAbsolutePath());
    ixConnect.download(url, temp);
    return temp;
},

Delete file: The deleteFile() function expects a Java file object as a parameter (not a string) and deletes this file.

deleteFile: function(delFile) {
    delFile["delete"]();
}

# run: Runtime Utilities

This module contains routines for access to the Java runtime. This allows external processes to be started or the current memory status to be queried.

Start process: The execute(command) command starts an external process. ELOas waits for the this call to finish and only then does it continue processing. This allows actions in this process to be evaluated as well.

log.debug("Process: " + NAME );
run.execute("C:\\ Tools\\BAT\\dirlist.bat");
log.debug("Read Result");
var txt = dex.asString("dirlist.txt");

Query free and available memory: The freeMemory() and maxMemory() commands display the currently available free memory and the maximum available memory.

log.debug "freeMemory: " + run.freeMemory() +
        ", maxMemory: " + run.maxMemory());
Last updated: September 26, 2023 at 7:46 AM