E-mail notifications

Weird thing, I’m developing on Documentum already more than 7 years, have taken part in a lot of projects, but have never seen user-friendly e-mail notifications for Documentum events. The OOTB implementation from EMC looks like someone invented a time-machine and sends e-mails from ninetieth:



Moreover, in recent Documentum releases even such elementary notifications have stopped working. Yes, you could say that we can take advantage of do_mail servlet capabilities (dm_event_template_sender method) for workflow notifications, but, at first, it is designed for workflow events only, at second, it’s vulnerable :(. So, I decided to implement my own e-mail notifications. Why is it important to send nice e-mails to users? At first, it’s a shame to mock business-users by sending them “computer-readable data”, at second, nice emails add some performance benefit to both business-users and ECM system: users do not waste time for logging into ECM, because they can get all required information from e-mail, and, so, ECM gets less user’s clicks 🙂 After some trials and errors, I realized that the best option to achieve my goal is use some scripting language, like javascript, python or ruby, but finally I have stopped on groovy. The main idea is use GStringTemplateEngine to process templates, below you can find simple “hello world” interop between java, groovy and DFC:

import java.util.HashMap;
import java.util.Map;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import com.documentum.com.DfClientX;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSysObject;
import com.documentum.fc.common.DfLoginInfo;

import groovy.text.GStringTemplateEngine;

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static final Class<?> TEMPLATE_CLASS = GStringTemplateEngine.class;

    public static final String FUNCTION_NAME = "process";

    private static Exception _initializationException;

    private static ScriptEngine _groovyTemplateEngine;

    static {
        try {
            ScriptEngineManager factory = new ScriptEngineManager(
                    Test.class.getClassLoader());
            _groovyTemplateEngine = factory.getEngineByName("groovy");
            _groovyTemplateEngine.eval(buildScript());
        } catch (ScriptException ex) {
            _groovyTemplateEngine = null;
            _initializationException = ex;
        }
    }

    public static void main(String[] args) throws Exception {
        IDfSession session = new DfClientX().getLocalClient().newSession(
                "ssc_dev", new DfLoginInfo("dmadmin", "dmadmin"));
        IDfSysObject object = (IDfSysObject) session
                .getObjectByQualification("dm_document");
        object.getFolderIdCount();
        Map<String, Object> bindings = new HashMap<String, Object>();
        bindings.put("object", object);
        System.out.println(process(
            "Hello, ${object.objectId}!\n\n"
            + "Your name is \"${object.objectName}\"\n"
            + "\nWhat about your locations?\n\n"
            + "<% for (i in 0..object.folderIdCount-1) {\n"
            + "def folder = object.session.getObject(object.getFolderId(i)) %>"
            + "${folder?.getFolderPath(0)}\n<% } %>",
                bindings));
    }

    public static String process(String script, Map<String, ?> bindings)
        throws Exception {
        if (_groovyTemplateEngine == null) {
            throw _initializationException;
        }
        Object[] params = {script, bindings };
        return (String) ((Invocable) _groovyTemplateEngine).invokeFunction(
                FUNCTION_NAME, params);
    }

    private static String buildScript() {
        StringBuilder script = new StringBuilder();
        script.append("import ").append(TEMPLATE_CLASS.getName()).append("\n");
        script.append("def ").append(FUNCTION_NAME)
                .append("(String text, Map bindings) {\n");
        script.append("    def engine = new ")
                .append(TEMPLATE_CLASS.getSimpleName()).append("()\n");
        script.append("    def template = engine.createTemplate(text).make(bindings)\n");
        script.append("    return template.toString()\n");
        script.append("}\n");
        script.append("\n");
        script.append("def ").append(FUNCTION_NAME)
                .append("(Reader reader, Map bindings) {\n");
        script.append("    def engine = new ")
                .append(TEMPLATE_CLASS.getSimpleName()).append("()\n");
        script.append("    def template = engine.createTemplate(reader).make(bindings)\n");
        script.append("    return template.toString()\n");
        script.append("}\n");
        script.append("\n");
        script.append("def ").append(FUNCTION_NAME)
                .append("(File file, Map bindings) {\n");
        script.append("    def engine = new ")
                .append(TEMPLATE_CLASS.getSimpleName()).append("()\n");
        script.append("    def template = engine.createTemplate(file).make(bindings)\n");
        script.append("    return template.toString()\n");
        script.append("}\n");
        return script.toString();
    }

}

Above code prints something like:

Hello, 0901fd088000033e!

Your name is "BPM Runtime"

What about your locations?

/System/Modules/Aspect/com.documentum.bpm.timer.aspect.CompleteWorkitemsTimerAspect
/System/Modules/Aspect/com.documentum.bpm.timer.aspect.DelegateWorkitemsTimerAspect
/System/Modules/Aspect/com.documentum.bpm.timer.aspect.NotificationTimerAspect
/System/Modules/Aspect/com.documentum.bpm.timer.aspect.DoMethodTimerAspect
/System/Modules/Aspect/com.documentum.bpm.timer.aspect.StartProcessTimerAspect
/System/Modules/TBO/dmc_transition_condition

And now ECM-administrators are able to setup any e-mail notifications they want:

UPD. CodeMirror tag implementation for wdk (requires codemirror and requirejs):

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class CodeMirrorTextArea extends TextArea {

    private String _mode;

    public CodeMirrorTextArea() {
        super();
    }

    public String getMode() {
        return _mode;
    }

    public void setMode(String mode) {
        _mode = mode;
    }

}
public class CodeMirrorTextAreaTag extends TextAreaTag {

    public static final String CODE_MIRROR = "/bvcore/js/codemirror/4.4";

    private String _mode;

    public CodeMirrorTextAreaTag() {
        super();
    }

    @Override
    protected Class<? extends Control> getControlClass() {
        return CodeMirrorTextArea.class;
    }

    @Override
    protected void setControlProperties(Control control) {
        super.setControlProperties(control);
        if (StringUtils.isNotBlank(getMode())) {
            ((CodeMirrorTextArea) control).setMode(getMode());
        }
    }

    @Override
    protected void renderEnd(JspWriter out) throws IOException {
        super.renderEnd(out);
        renderCodeMirror(out);
    }

    public String getMode() {
        return _mode;
    }

    public void setMode(String mode) {
        _mode = mode;
    }

    protected void renderCodeMirror(JspWriter out) throws IOException {
        StringBuilder buf = new StringBuilder();
        CodeMirrorTextArea control = (CodeMirrorTextArea) getControl();

        String mode = control.getMode();

        if (StringUtils.isBlank(mode)) {
            return;
        }

        buf.append("\n<script type=\"text/javascript\">");
        buf.append("\n\trequire.config({");
        buf.append("\n\t\tdeps: [],");
        buf.append("\n\t\tpaths: {");
        buf.append("\n\t\t\tcm: \"")
                .append(Form.makeUrl(pageContext.getRequest(), CODE_MIRROR))
                .append("\"");
        buf.append("\n\t\t}");
        buf.append("\n\t});");
        buf.append("\n\trequire([");
        buf.append("\n\t\t\"");
        buf.append("cm/lib/codemirror");
        buf.append("\", \"cm/mode").append("/").append(mode).append("/")
                .append(mode);
        buf.append("\"");
        buf.append("\n\t], function(CodeMirror) {");
        buf.append("\n\t\tvar textArea = document.getElementsByName(\"")
                .append(control.getElementName()).append("\")[0];");
        buf.append("\n\t\tvar height = textArea.offsetHeight;");
        buf.append("\n\t\tvar width = textArea.offsetWidth;");
        buf.append("\n\t\tvar editor = CodeMirror.fromTextArea(textArea, {");
        buf.append("\n\t\t\tlineNumbers: true,");
        buf.append("\n\t\t\tmode: \"").append(mode).append("\"");
        buf.append("\n\t\t});");
        buf.append("\n\t\teditor.setSize(width,height);");
        buf.append("\n\t\teditor.refresh();");
        buf.append("\n\t});");
        buf.append("\n</script>");
        out.write(buf.toString());
    }
}

3 thoughts on “E-mail notifications

  1. Pingback: Time-machine in EMC | Documentum in a (nuts)HELL
  2. is that codemirror what you’re using to edit the template? Looks great, I’ve done something quite similar (without groovy :P). It seems in the end everyone has to implement its own ldap, mail notifications… EMC should take note…

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s